@pixelated-tech/components 3.5.14 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/components/admin/site-health/google.api.integration.js +258 -0
- package/dist/components/admin/site-health/google.api.utils.js +47 -0
- package/dist/components/admin/site-health/site-health-accessibility.js +4 -10
- package/dist/components/admin/site-health/site-health-axe-core.js +4 -10
- package/dist/components/admin/site-health/site-health-cloudwatch.js +24 -26
- package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +4 -10
- package/dist/components/admin/site-health/site-health-github.js +8 -14
- package/dist/components/admin/site-health/site-health-google-analytics.integration.js +1 -107
- package/dist/components/admin/site-health/site-health-google-analytics.js +21 -29
- package/dist/components/admin/site-health/site-health-google-search-console.integration.js +1 -113
- package/dist/components/admin/site-health/site-health-google-search-console.js +22 -28
- package/dist/components/admin/site-health/site-health-on-site-seo.js +10 -33
- package/dist/components/admin/site-health/site-health-overview.js +4 -10
- package/dist/components/admin/site-health/site-health-performance.js +4 -10
- package/dist/components/admin/site-health/site-health-security.js +6 -10
- package/dist/components/admin/site-health/site-health-seo.js +4 -10
- package/dist/components/admin/site-health/site-health-template.js +68 -43
- package/dist/components/admin/site-health/site-health-uptime.js +4 -9
- package/dist/components/admin/site-health/site-health.css +7 -0
- package/dist/index.adminclient.js +2 -6
- package/dist/index.adminserver.js +6 -5
- package/dist/index.js +39 -45
- package/dist/types/components/admin/site-health/google.api.integration.d.ts +82 -0
- package/dist/types/components/admin/site-health/google.api.integration.d.ts.map +1 -0
- package/dist/types/components/admin/site-health/google.api.utils.d.ts +32 -0
- package/dist/types/components/admin/site-health/google.api.utils.d.ts.map +1 -0
- package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-axe-core.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-github.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts +1 -21
- package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts +1 -41
- package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-template.d.ts +8 -1
- package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-uptime.d.ts.map +1 -1
- package/dist/types/index.adminclient.d.ts +2 -6
- package/dist/types/index.adminserver.d.ts +6 -5
- package/dist/types/index.d.ts +38 -44
- package/dist/types/stories/admin/site-health.stories.d.ts.map +1 -1
- package/dist/types/tests/google.api.integration.test.d.ts +2 -0
- package/dist/types/tests/google.api.integration.test.d.ts.map +1 -0
- package/dist/types/tests/google.api.utils.test.d.ts +5 -0
- package/dist/types/tests/google.api.utils.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/components/admin/site-health/google-api-auth.js +0 -69
- package/dist/types/components/admin/site-health/google-api-auth.d.ts +0 -37
- package/dist/types/components/admin/site-health/google-api-auth.d.ts.map +0 -1
|
@@ -9,41 +9,33 @@ SiteHealthGoogleAnalytics.propTypes = {
|
|
|
9
9
|
endDate: PropTypes.string,
|
|
10
10
|
};
|
|
11
11
|
export function SiteHealthGoogleAnalytics({ siteName, startDate, endDate }) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const result = await response.json();
|
|
23
|
-
if (!result.success) {
|
|
24
|
-
// Handle specific error types
|
|
25
|
-
if (result.error?.includes('invalid_grant') || result.error?.includes('authentication')) {
|
|
26
|
-
throw new Error('Google Analytics authentication expired. Please re-authorize the application.');
|
|
27
|
-
}
|
|
28
|
-
else if (result.error?.includes('GA4 Property ID not configured')) {
|
|
29
|
-
throw new Error('GA4 Property ID not configured for this site');
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
throw new Error(result.error || 'Failed to load analytics data');
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return result.data;
|
|
36
|
-
};
|
|
37
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Analytics", columnSpan: 2, fetchData: fetchAnalyticsData, children: (data) => {
|
|
38
|
-
if (!data || data.length === 0) {
|
|
12
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Analytics", columnSpan: 2, endpoint: {
|
|
13
|
+
endpoint: '/api/site-health/google-analytics',
|
|
14
|
+
params: {
|
|
15
|
+
...(startDate && { startDate }),
|
|
16
|
+
...(endDate && { endDate }),
|
|
17
|
+
},
|
|
18
|
+
responseTransformer: (result) => result.data, // Extract the data array from the response
|
|
19
|
+
}, children: (data) => {
|
|
20
|
+
// Ensure data is an array
|
|
21
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
39
22
|
return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "No data available for the selected date range" }) }));
|
|
40
23
|
}
|
|
41
|
-
|
|
24
|
+
// Filter out any invalid data points
|
|
25
|
+
const validData = data.filter((point) => point &&
|
|
26
|
+
typeof point === 'object' &&
|
|
27
|
+
typeof point.date === 'string' &&
|
|
28
|
+
typeof point.currentPageViews === 'number' &&
|
|
29
|
+
typeof point.previousPageViews === 'number');
|
|
30
|
+
if (validData.length === 0) {
|
|
31
|
+
return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "Invalid data format received from Google Analytics API." }) }));
|
|
32
|
+
}
|
|
33
|
+
return (_jsx("div", { children: _jsx("div", { style: { width: '100%', height: '400px', border: '1px solid #ddd' }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: validData, margin: { top: 40, right: 30, left: 20, bottom: 5 }, children: [_jsx("text", { x: "50%", y: 20, textAnchor: "middle", fontSize: "16", fontWeight: "bold", fill: "#374151", children: "Page Views (Current vs Previous Period)" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 } }), _jsx(Tooltip, { formatter: (value, name) => [
|
|
42
34
|
value?.toLocaleString() || '0',
|
|
43
35
|
name || 'Unknown'
|
|
44
36
|
], labelFormatter: (label) => `Date: ${label}` }), _jsx(Legend, { wrapperStyle: {
|
|
45
37
|
fontSize: '12px',
|
|
46
38
|
paddingTop: '10px'
|
|
47
|
-
} }), _jsx(Bar, { dataKey: "currentPageViews", fill: "#3b82f6", name: "Current Period", radius: [2, 2, 0, 0] }), _jsx(Line, { type: "monotone", dataKey: "previousPageViews", stroke: "#ef4444", strokeWidth: 2, strokeDasharray: "5 5", dot: { fill: '#ef4444', strokeWidth: 2, r: 3 }, activeDot: { r: 5, stroke: '#ef4444', strokeWidth: 2 }, name: "Previous Period" })] }, `chart-${
|
|
39
|
+
} }), _jsx(Bar, { dataKey: "currentPageViews", fill: "#3b82f6", name: "Current Period", radius: [2, 2, 0, 0] }), _jsx(Line, { type: "monotone", dataKey: "previousPageViews", stroke: "#ef4444", strokeWidth: 2, strokeDasharray: "5 5", dot: { fill: '#ef4444', strokeWidth: 2, r: 3 }, activeDot: { r: 5, stroke: '#ef4444', strokeWidth: 2 }, name: "Previous Period" })] }, `chart-${validData.length}`) }) }) }));
|
|
48
40
|
} }));
|
|
49
41
|
}
|
|
@@ -3,116 +3,4 @@
|
|
|
3
3
|
* Server-side utilities for Google Search Console data retrieval
|
|
4
4
|
*/
|
|
5
5
|
"use server";
|
|
6
|
-
|
|
7
|
-
import { createSearchConsoleClient } from './google-api-auth';
|
|
8
|
-
// Cache for search console data (1 hour)
|
|
9
|
-
const searchConsoleCache = new RouteCache();
|
|
10
|
-
/**
|
|
11
|
-
* Get Google Search Console data for a site with current/previous period comparison
|
|
12
|
-
*/
|
|
13
|
-
export async function getSearchConsoleData(config, siteName, startDate, endDate) {
|
|
14
|
-
try {
|
|
15
|
-
// Check cache first
|
|
16
|
-
const cacheKey = `searchconsole-${siteName}-${startDate || 'default'}-${endDate || 'default'}`;
|
|
17
|
-
const cached = searchConsoleCache.get(cacheKey);
|
|
18
|
-
if (cached) {
|
|
19
|
-
return { success: true, data: cached };
|
|
20
|
-
}
|
|
21
|
-
if (!config.siteUrl) {
|
|
22
|
-
return {
|
|
23
|
-
success: false,
|
|
24
|
-
error: 'Site URL not configured for Search Console'
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
// Set up authentication
|
|
28
|
-
const authResult = await createSearchConsoleClient(config);
|
|
29
|
-
if (!authResult.success) {
|
|
30
|
-
return {
|
|
31
|
-
success: false,
|
|
32
|
-
error: authResult.error || 'Authentication failed'
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
const searchconsole = authResult.client;
|
|
36
|
-
// Calculate date ranges
|
|
37
|
-
const currentEndDate = endDate ? new Date(endDate) : new Date();
|
|
38
|
-
const currentStartDate = startDate ? new Date(startDate) : new Date(currentEndDate.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
39
|
-
// Calculate previous period (same duration before the current period)
|
|
40
|
-
const periodDuration = currentEndDate.getTime() - currentStartDate.getTime();
|
|
41
|
-
const previousEndDate = new Date(currentStartDate.getTime() - 24 * 60 * 60 * 1000); // One day before start
|
|
42
|
-
const previousStartDate = new Date(previousEndDate.getTime() - periodDuration);
|
|
43
|
-
const currentStartStr = currentStartDate.toISOString().split('T')[0];
|
|
44
|
-
const currentEndStr = currentEndDate.toISOString().split('T')[0];
|
|
45
|
-
const previousStartStr = previousStartDate.toISOString().split('T')[0];
|
|
46
|
-
const previousEndStr = previousEndDate.toISOString().split('T')[0];
|
|
47
|
-
// Fetch current period data
|
|
48
|
-
const currentResponse = await searchconsole.searchanalytics.query({
|
|
49
|
-
siteUrl: config.siteUrl,
|
|
50
|
-
requestBody: {
|
|
51
|
-
startDate: currentStartStr,
|
|
52
|
-
endDate: currentEndStr,
|
|
53
|
-
dimensions: ['date'],
|
|
54
|
-
rowLimit: 10000,
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
// Fetch previous period data
|
|
58
|
-
const previousResponse = await searchconsole.searchanalytics.query({
|
|
59
|
-
siteUrl: config.siteUrl,
|
|
60
|
-
requestBody: {
|
|
61
|
-
startDate: previousStartStr,
|
|
62
|
-
endDate: previousEndStr,
|
|
63
|
-
dimensions: ['date'],
|
|
64
|
-
rowLimit: 10000,
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
// Create a map of previous period data by date
|
|
68
|
-
const previousDataMap = new Map();
|
|
69
|
-
previousResponse.data.rows?.forEach((row) => {
|
|
70
|
-
const dateStr = row.keys?.[0] || '';
|
|
71
|
-
if (dateStr) {
|
|
72
|
-
previousDataMap.set(dateStr, {
|
|
73
|
-
clicks: parseFloat(String(row.clicks || '0')),
|
|
74
|
-
impressions: parseFloat(String(row.impressions || '0'))
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
// Combine current and previous period data
|
|
79
|
-
const chartData = [];
|
|
80
|
-
const daysInRange = Math.ceil((currentEndDate.getTime() - currentStartDate.getTime()) / (24 * 60 * 60 * 1000));
|
|
81
|
-
for (let i = daysInRange - 1; i >= 0; i--) {
|
|
82
|
-
const currentDate = new Date(currentEndDate);
|
|
83
|
-
currentDate.setDate(currentDate.getDate() - i);
|
|
84
|
-
const currentDateStr = currentDate.toISOString().split('T')[0];
|
|
85
|
-
// Calculate corresponding previous period date
|
|
86
|
-
const previousDate = new Date(currentDate.getTime() - periodDuration);
|
|
87
|
-
const previousDateStr = previousDate.toISOString().split('T')[0];
|
|
88
|
-
// Get current period data
|
|
89
|
-
const currentRow = currentResponse.data.rows?.find((row) => row.keys?.[0] === currentDateStr);
|
|
90
|
-
const currentClicks = parseFloat(String(currentRow?.clicks || '0'));
|
|
91
|
-
const currentImpressions = parseFloat(String(currentRow?.impressions || '0'));
|
|
92
|
-
// Get previous period data
|
|
93
|
-
const previousData = previousDataMap.get(previousDateStr) || { clicks: 0, impressions: 0 };
|
|
94
|
-
// Format date for display
|
|
95
|
-
const formattedDate = currentDate.toLocaleDateString('en-US', {
|
|
96
|
-
month: 'short',
|
|
97
|
-
day: 'numeric'
|
|
98
|
-
});
|
|
99
|
-
chartData.push({
|
|
100
|
-
date: formattedDate,
|
|
101
|
-
currentImpressions: Math.round(currentImpressions),
|
|
102
|
-
currentClicks: Math.round(currentClicks),
|
|
103
|
-
previousImpressions: Math.round(previousData.impressions),
|
|
104
|
-
previousClicks: Math.round(previousData.clicks),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
// Cache the result
|
|
108
|
-
searchConsoleCache.set(cacheKey, chartData);
|
|
109
|
-
return { success: true, data: chartData };
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
console.error('Google Search Console error:', error);
|
|
113
|
-
return {
|
|
114
|
-
success: false,
|
|
115
|
-
error: error.message
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
6
|
+
export {};
|
|
@@ -9,36 +9,30 @@ SiteHealthGoogleSearchConsole.propTypes = {
|
|
|
9
9
|
endDate: PropTypes.string,
|
|
10
10
|
};
|
|
11
11
|
export function SiteHealthGoogleSearchConsole({ siteName, startDate, endDate }) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const result = await response.json();
|
|
23
|
-
if (!result.success) {
|
|
24
|
-
// Handle specific error types
|
|
25
|
-
if (result.error?.includes('invalid_grant') || result.error?.includes('authentication')) {
|
|
26
|
-
throw new Error('Google Search Console authentication expired. Please re-authorize the application.');
|
|
27
|
-
}
|
|
28
|
-
else if (result.error?.includes('GSC Site URL not configured')) {
|
|
29
|
-
throw new Error('GSC Site URL not configured for this site');
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
throw new Error(result.error || 'Failed to load search console data');
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return result.data;
|
|
36
|
-
};
|
|
37
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Search Console", columnSpan: 2, fetchData: fetchSearchConsoleData, children: (data) => {
|
|
38
|
-
if (!data || data.length === 0) {
|
|
12
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Search Console", columnSpan: 2, endpoint: {
|
|
13
|
+
endpoint: '/api/site-health/google-search-console',
|
|
14
|
+
params: {
|
|
15
|
+
...(startDate && { startDate }),
|
|
16
|
+
...(endDate && { endDate }),
|
|
17
|
+
},
|
|
18
|
+
responseTransformer: (result) => result.data, // Extract the data array from the response
|
|
19
|
+
}, children: (data) => {
|
|
20
|
+
// Ensure data is an array
|
|
21
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
39
22
|
return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "No indexing data available for the selected date range" }) }));
|
|
40
23
|
}
|
|
41
|
-
|
|
24
|
+
// Filter out any invalid data points
|
|
25
|
+
const validData = data.filter((point) => point &&
|
|
26
|
+
typeof point === 'object' &&
|
|
27
|
+
typeof point.date === 'string' &&
|
|
28
|
+
typeof point.currentImpressions === 'number' &&
|
|
29
|
+
typeof point.currentClicks === 'number' &&
|
|
30
|
+
typeof point.previousImpressions === 'number' &&
|
|
31
|
+
typeof point.previousClicks === 'number');
|
|
32
|
+
if (validData.length === 0) {
|
|
33
|
+
return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "Invalid data format received from Google Search Console API." }) }));
|
|
34
|
+
}
|
|
35
|
+
return (_jsx("div", { children: _jsx("div", { style: { width: '100%', height: '400px', border: '1px solid #ddd' }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: validData, margin: { top: 40, right: 30, left: 20, bottom: 5 }, children: [_jsx("text", { x: "50%", y: 20, textAnchor: "middle", fontSize: "16", fontWeight: "bold", fill: "#374151", children: "Impressions vs Clicks (Current vs Previous Period)" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 } }), _jsx(Tooltip, { formatter: (value, name) => [
|
|
42
36
|
value?.toLocaleString() || '0',
|
|
43
37
|
name || 'Unknown'
|
|
44
38
|
], labelFormatter: (label) => `Date: ${label}` }), _jsx(Legend, { wrapperStyle: {
|
|
@@ -61,42 +61,14 @@ function restructureAuditsByType(pagesAnalyzed) {
|
|
|
61
61
|
});
|
|
62
62
|
return restructuredAudits.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
63
63
|
}
|
|
64
|
-
// Fetch real SEO data from API
|
|
65
|
-
async function fetchOnSiteSEOData(siteName) {
|
|
66
|
-
try {
|
|
67
|
-
const response = await fetch(`/api/site-health/on-site-seo?siteName=${encodeURIComponent(siteName)}`);
|
|
68
|
-
if (!response.ok) {
|
|
69
|
-
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
70
|
-
}
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
if (data.status === 'error') {
|
|
73
|
-
throw new Error(data.error || 'SEO analysis failed');
|
|
74
|
-
}
|
|
75
|
-
// Process data to aggregate audits by type across all pages
|
|
76
|
-
// const aggregatedOnPageAudits = restructureAuditsByType(data.pagesAnalyzed);
|
|
77
|
-
return data;
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.error('Error fetching SEO data:', error);
|
|
81
|
-
// Return error structure that the template can display
|
|
82
|
-
return {
|
|
83
|
-
site: siteName,
|
|
84
|
-
url: '',
|
|
85
|
-
overallScore: null,
|
|
86
|
-
pagesAnalyzed: [],
|
|
87
|
-
onSiteAudits: [],
|
|
88
|
-
totalPages: 0,
|
|
89
|
-
timestamp: new Date().toISOString(),
|
|
90
|
-
status: 'error',
|
|
91
|
-
error: error instanceof Error ? error.message : 'Failed to fetch SEO data'
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
64
|
SiteHealthOnSiteSEO.propTypes = {
|
|
96
65
|
siteName: PropTypes.string.isRequired,
|
|
97
66
|
};
|
|
98
67
|
export function SiteHealthOnSiteSEO({ siteName }) {
|
|
99
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "On-Site SEO",
|
|
68
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "On-Site SEO", endpoint: {
|
|
69
|
+
endpoint: '/api/site-health/on-site-seo',
|
|
70
|
+
responseTransformer: (result) => result.data, // Extract the data from the response
|
|
71
|
+
}, children: (data) => {
|
|
100
72
|
if (!data)
|
|
101
73
|
return null;
|
|
102
74
|
if (data.status === 'error') {
|
|
@@ -147,7 +119,12 @@ export function SiteHealthOnSiteSEO({ siteName }) {
|
|
|
147
119
|
// Fallback to original formatting
|
|
148
120
|
return formatAuditItem(item);
|
|
149
121
|
};
|
|
150
|
-
return (_jsxs(_Fragment, { children: [
|
|
122
|
+
return (_jsxs(_Fragment, { children: [aggregatedOnPageAudits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Page SEO Audits" }), _jsx("div", { className: "health-audit-list", children: aggregatedOnPageAudits
|
|
123
|
+
.filter(audit => audit.scoreDisplayMode !== 'notApplicable')
|
|
124
|
+
.sort((a, b) => (b.score || 0) - (a.score || 0))
|
|
125
|
+
.map((audit) => (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", children: getAuditScoreIcon(audit.score) }), _jsxs("div", { className: "health-audit-content", children: [_jsxs("span", { className: "health-audit-title", children: [audit.score === null ? '(N/A)' : `(${Math.round((audit.score || 0) * 100)}%)`, " ", audit.title] }), audit.displayValue && audit.score !== 1 && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details && audit.details.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && audit.score !== 1 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items
|
|
126
|
+
.filter((item) => item.score !== 1)
|
|
127
|
+
.map((item, idx) => (_jsx("div", { style: { marginBottom: '0.125rem' }, children: formatPageIssue(item) }, idx))) }) }))] })] }, audit.id))) })] })), data.onSiteAudits.length > 0 && (_jsxs("div", { style: { marginTop: aggregatedOnPageAudits.length > 0 ? '2rem' : '0' }, children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Site SEO Audits" }), _jsx("div", { className: "health-audit-list", children: data.onSiteAudits
|
|
151
128
|
.filter(audit => audit.scoreDisplayMode !== 'notApplicable')
|
|
152
129
|
.sort((a, b) => (b.score || 0) - (a.score || 0))
|
|
153
130
|
.map((audit) => (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", children: getAuditScoreIcon(audit.score) }), _jsxs("div", { className: "health-audit-content", children: [_jsxs("span", { className: "health-audit-title", children: [audit.score === null ? '(N/A)' : `(${Math.round((audit.score || 0) * 100)}%)`, " ", audit.title] }), audit.displayValue && audit.score !== 1 && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details && audit.details.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && audit.score !== 1 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback } from 'react';
|
|
4
3
|
import PropTypes from 'prop-types';
|
|
5
4
|
import { SiteHealthTemplate } from './site-health-template';
|
|
6
5
|
import { formatScore, getScoreColor } from './site-health-utils';
|
|
@@ -8,15 +7,10 @@ SiteHealthOverview.propTypes = {
|
|
|
8
7
|
siteName: PropTypes.string.isRequired,
|
|
9
8
|
};
|
|
10
9
|
export function SiteHealthOverview({ siteName }) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
throw new Error(result.error || 'Failed to fetch Core Web Vitals data');
|
|
16
|
-
}
|
|
17
|
-
return result;
|
|
18
|
-
}, []);
|
|
19
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Site Overview", fetchData: fetchCWVData, children: (data) => {
|
|
10
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Site Overview", endpoint: {
|
|
11
|
+
endpoint: '/api/site-health/core-web-vitals',
|
|
12
|
+
responseTransformer: (result) => result, // Result is already in the correct format
|
|
13
|
+
}, children: (data) => {
|
|
20
14
|
if (!data?.data || data.data.length === 0) {
|
|
21
15
|
return (_jsx("p", { style: { color: '#6b7280' }, children: "No site health data available for this site." }));
|
|
22
16
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback } from 'react';
|
|
4
3
|
import PropTypes from 'prop-types';
|
|
5
4
|
import { SiteHealthTemplate } from './site-health-template';
|
|
6
5
|
import { formatAuditItem, getAuditScoreIcon, getScoreColor, formatScore } from './site-health-utils';
|
|
@@ -8,15 +7,10 @@ SiteHealthPerformance.propTypes = {
|
|
|
8
7
|
siteName: PropTypes.string.isRequired,
|
|
9
8
|
};
|
|
10
9
|
export function SiteHealthPerformance({ siteName }) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
throw new Error(result.error || 'Failed to fetch Core Web Vitals data');
|
|
16
|
-
}
|
|
17
|
-
return result;
|
|
18
|
-
}, []);
|
|
19
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Performance", fetchData: fetchCWVData, children: (data) => {
|
|
10
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Performance", endpoint: {
|
|
11
|
+
endpoint: '/api/site-health/core-web-vitals',
|
|
12
|
+
responseTransformer: (result) => result, // Result is already in the correct format
|
|
13
|
+
}, children: (data) => {
|
|
20
14
|
if (!data?.data || data.data.length === 0) {
|
|
21
15
|
return (_jsx("p", { style: { color: '#6b7280' }, children: "No site health data available for this site." }));
|
|
22
16
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback } from 'react';
|
|
4
3
|
import PropTypes from 'prop-types';
|
|
5
4
|
import { SiteHealthTemplate } from './site-health-template';
|
|
6
5
|
import { formatAuditItem, getAuditScoreIcon, getScoreColor } from './site-health-utils';
|
|
@@ -8,15 +7,12 @@ SiteHealthSecurity.propTypes = {
|
|
|
8
7
|
siteName: PropTypes.string.isRequired,
|
|
9
8
|
};
|
|
10
9
|
export function SiteHealthSecurity({ siteName }) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
}, []);
|
|
19
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Site Security", fetchData: fetchSecurityData, children: (data) => {
|
|
10
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Security", endpoint: {
|
|
11
|
+
endpoint: '/api/site-health/core-web-vitals',
|
|
12
|
+
responseTransformer: (result) => ({
|
|
13
|
+
psiData: result.success ? result : undefined
|
|
14
|
+
}),
|
|
15
|
+
}, children: (data) => {
|
|
20
16
|
const psiData = data?.psiData?.data?.[0];
|
|
21
17
|
if (!psiData) {
|
|
22
18
|
return (_jsx("p", { style: { color: '#6b7280' }, children: "No security data available for this site." }));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback } from 'react';
|
|
4
3
|
import PropTypes from 'prop-types';
|
|
5
4
|
import { SiteHealthTemplate } from './site-health-template';
|
|
6
5
|
import { formatAuditItem, getAuditScoreIcon, getScoreColor } from './site-health-utils';
|
|
@@ -8,15 +7,10 @@ SiteHealthSEO.propTypes = {
|
|
|
8
7
|
siteName: PropTypes.string.isRequired,
|
|
9
8
|
};
|
|
10
9
|
export function SiteHealthSEO({ siteName }) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
throw new Error(result.error || 'Failed to fetch SEO data');
|
|
16
|
-
}
|
|
17
|
-
return result;
|
|
18
|
-
}, []);
|
|
19
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - SEO", fetchData: fetchSEOData, children: (data) => {
|
|
10
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - SEO", endpoint: {
|
|
11
|
+
endpoint: '/api/site-health/core-web-vitals',
|
|
12
|
+
responseTransformer: (result) => result, // Result is already in the correct format
|
|
13
|
+
}, children: (data) => {
|
|
20
14
|
if (!data?.data || data.data.length === 0) {
|
|
21
15
|
return (_jsx("p", { style: { color: '#6b7280' }, children: "No SEO data available for this site." }));
|
|
22
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import { PageGridItem } from '../../general/semantic';
|
|
6
6
|
import "./site-health.css";
|
|
@@ -8,7 +8,14 @@ SiteHealthTemplate.propTypes = {
|
|
|
8
8
|
siteName: PropTypes.string.isRequired,
|
|
9
9
|
title: PropTypes.string,
|
|
10
10
|
children: PropTypes.func.isRequired,
|
|
11
|
-
|
|
11
|
+
endpoint: PropTypes.shape({
|
|
12
|
+
endpoint: PropTypes.string.isRequired,
|
|
13
|
+
method: PropTypes.oneOf(['GET', 'POST', 'PUT', 'DELETE']),
|
|
14
|
+
headers: PropTypes.object,
|
|
15
|
+
params: PropTypes.object,
|
|
16
|
+
body: PropTypes.any,
|
|
17
|
+
responseTransformer: PropTypes.func,
|
|
18
|
+
}),
|
|
12
19
|
enableCacheControl: PropTypes.bool,
|
|
13
20
|
columnSpan: PropTypes.number,
|
|
14
21
|
};
|
|
@@ -17,49 +24,67 @@ export function SiteHealthTemplate(props) {
|
|
|
17
24
|
const [data, setData] = useState(null);
|
|
18
25
|
const [loading, setLoading] = useState(false);
|
|
19
26
|
const [error, setError] = useState(null);
|
|
27
|
+
// Default fetch function for endpoint-based requests
|
|
28
|
+
const fetchFromEndpoint = useCallback(async (useCache = true) => {
|
|
29
|
+
const { endpoint: endpointUrl, method = 'GET', headers = {}, params = {}, body, responseTransformer } = typedProps.endpoint;
|
|
30
|
+
// Build URL with siteName parameter
|
|
31
|
+
const url = new URL(endpointUrl, window.location.origin);
|
|
32
|
+
url.searchParams.set('siteName', encodeURIComponent(typedProps.siteName));
|
|
33
|
+
// Add additional params
|
|
34
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
35
|
+
url.searchParams.set(key, value);
|
|
36
|
+
});
|
|
37
|
+
// Add cache control if not using cache
|
|
38
|
+
if (!useCache) {
|
|
39
|
+
url.searchParams.set('cache', 'false');
|
|
40
|
+
}
|
|
41
|
+
const response = await fetch(url.toString(), {
|
|
42
|
+
method,
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
...headers,
|
|
46
|
+
},
|
|
47
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
51
|
+
}
|
|
52
|
+
const result = await response.json();
|
|
53
|
+
if (!result.success) {
|
|
54
|
+
throw new Error(result.error || 'API request failed');
|
|
55
|
+
}
|
|
56
|
+
// Apply response transformer if provided
|
|
57
|
+
return responseTransformer ? responseTransformer(result) : result;
|
|
58
|
+
}, [typedProps.endpoint, typedProps.siteName]);
|
|
59
|
+
const loadData = useCallback(async () => {
|
|
60
|
+
if (!typedProps.siteName) {
|
|
61
|
+
setData(null);
|
|
62
|
+
setLoading(false);
|
|
63
|
+
setError(null);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setLoading(true);
|
|
67
|
+
setError(null);
|
|
68
|
+
try {
|
|
69
|
+
// Check for cache control from URL query parameters
|
|
70
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
71
|
+
const cacheParam = urlParams.get('cache');
|
|
72
|
+
const useCache = typedProps.enableCacheControl ?? true ? (cacheParam !== 'false') : true;
|
|
73
|
+
const result = await fetchFromEndpoint(useCache);
|
|
74
|
+
setData(result);
|
|
75
|
+
setError(null);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
setError(err instanceof Error ? err.message : 'Failed to load data');
|
|
79
|
+
setData(null);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
setLoading(false);
|
|
83
|
+
}
|
|
84
|
+
}, [typedProps.siteName, fetchFromEndpoint, typedProps.enableCacheControl]);
|
|
20
85
|
useEffect(() => {
|
|
21
|
-
let isMounted = true;
|
|
22
|
-
const loadData = async () => {
|
|
23
|
-
if (!typedProps.siteName) {
|
|
24
|
-
if (isMounted) {
|
|
25
|
-
setData(null);
|
|
26
|
-
setLoading(false);
|
|
27
|
-
setError(null);
|
|
28
|
-
}
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (isMounted) {
|
|
32
|
-
setLoading(true);
|
|
33
|
-
setError(null);
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
// Check for cache control from URL query parameters
|
|
37
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
38
|
-
const cacheParam = urlParams.get('cache');
|
|
39
|
-
const useCache = typedProps.enableCacheControl ? (cacheParam !== 'false') : true;
|
|
40
|
-
const result = await typedProps.fetchData(typedProps.siteName, useCache);
|
|
41
|
-
if (isMounted) {
|
|
42
|
-
setData(result);
|
|
43
|
-
setError(null);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
if (isMounted) {
|
|
48
|
-
setError(err instanceof Error ? err.message : 'Failed to load data');
|
|
49
|
-
setData(null);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
finally {
|
|
53
|
-
if (isMounted) {
|
|
54
|
-
setLoading(false);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
86
|
loadData();
|
|
59
|
-
|
|
60
|
-
isMounted = false;
|
|
61
|
-
};
|
|
62
|
-
}, [typedProps.siteName, typedProps.fetchData]);
|
|
87
|
+
}, [loadData]);
|
|
63
88
|
// If no site selected, show nothing
|
|
64
89
|
if (!typedProps.siteName) {
|
|
65
90
|
return null;
|
|
@@ -6,15 +6,10 @@ SiteHealthUptime.propTypes = {
|
|
|
6
6
|
siteName: PropTypes.string.isRequired,
|
|
7
7
|
};
|
|
8
8
|
export function SiteHealthUptime({ siteName }) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
throw new Error('Failed to load health status');
|
|
14
|
-
}
|
|
15
|
-
return result;
|
|
16
|
-
};
|
|
17
|
-
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Health Status", fetchData: fetchUptimeData, children: (data) => {
|
|
9
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Health Status", endpoint: {
|
|
10
|
+
endpoint: '/api/site-health/uptime',
|
|
11
|
+
responseTransformer: (result) => result, // Result is already in the correct format
|
|
12
|
+
}, children: (data) => {
|
|
18
13
|
if (!data) {
|
|
19
14
|
return (_jsx("p", { style: { color: '#6b7280' }, children: "No uptime data available for this site." }));
|
|
20
15
|
}
|
|
@@ -17,15 +17,11 @@ export * from './components/admin/site-health/site-health-seo';
|
|
|
17
17
|
export * from './components/admin/site-health/site-health-template';
|
|
18
18
|
export * from './components/admin/site-health/site-health-uptime';
|
|
19
19
|
// Client-safe admin components (can run on both client and server)
|
|
20
|
+
export * from './components/admin/site-health/google.api.utils';
|
|
20
21
|
export * from './components/admin/site-health/seo-constants';
|
|
21
22
|
export * from './components/admin/site-health/site-health-cache';
|
|
22
23
|
export * from './components/admin/site-health/site-health-indicators';
|
|
23
24
|
export * from './components/admin/site-health/site-health-on-site-seo.integration';
|
|
24
|
-
export * from './components/admin/site-health/site-health-types';
|
|
25
|
-
export * from './components/admin/site-health/site-health-performance';
|
|
26
|
-
export * from './components/admin/site-health/site-health-utils';
|
|
27
|
-
export * from './components/admin/site-health/site-health-cache';
|
|
28
|
-
export * from './components/admin/site-health/site-health-indicators';
|
|
29
|
-
export * from './components/admin/site-health/site-health-on-site-seo.integration';
|
|
30
25
|
export * from './components/admin/site-health/site-health-performance';
|
|
31
26
|
export * from './components/admin/site-health/site-health-types';
|
|
27
|
+
export * from './components/admin/site-health/site-health-utils';
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
export * from './components/admin/componentusage/componentDiscovery';
|
|
2
1
|
export * from './components/admin/componentusage/componentAnalysis';
|
|
2
|
+
export * from './components/admin/componentusage/componentDiscovery';
|
|
3
3
|
export * from './components/admin/deploy/deployment.integration';
|
|
4
|
-
export * from './components/admin/site-health/google
|
|
4
|
+
export * from './components/admin/site-health/google.api.integration';
|
|
5
|
+
export * from './components/admin/site-health/google.api.utils';
|
|
6
|
+
export * from './components/admin/site-health/seo-constants';
|
|
5
7
|
export * from './components/admin/site-health/site-health-axe-core.integration';
|
|
6
8
|
export * from './components/admin/site-health/site-health-cache';
|
|
9
|
+
export * from './components/admin/site-health/site-health-cloudwatch.integration';
|
|
7
10
|
export * from './components/admin/site-health/site-health-core-web-vitals.integration';
|
|
8
11
|
export * from './components/admin/site-health/site-health-github.integration';
|
|
9
12
|
export * from './components/admin/site-health/site-health-google-analytics.integration';
|
|
10
13
|
export * from './components/admin/site-health/site-health-google-search-console.integration';
|
|
11
14
|
export * from './components/admin/site-health/site-health-indicators';
|
|
12
15
|
export * from './components/admin/site-health/site-health-on-site-seo.integration';
|
|
13
|
-
export * from './components/admin/site-health/site-health-cloudwatch.integration';
|
|
14
|
-
export * from './components/admin/site-health/seo-constants';
|
|
15
16
|
export * from './components/admin/site-health/site-health-security.integration';
|
|
16
17
|
export * from './components/admin/site-health/site-health-types';
|
|
17
|
-
export * from './components/admin/site-health/site-health-utils';
|
|
18
18
|
export * from './components/admin/site-health/site-health-uptime.integration';
|
|
19
|
+
export * from './components/admin/site-health/site-health-utils';
|
|
19
20
|
export * from './components/admin/sites/sites.integration';
|
|
20
21
|
export * from './components/cms/contentful.management';
|