@pixelated-tech/components 3.5.13 → 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.
Files changed (61) hide show
  1. package/README.md +2 -0
  2. package/dist/components/admin/site-health/google.api.integration.js +258 -0
  3. package/dist/components/admin/site-health/google.api.utils.js +47 -0
  4. package/dist/components/admin/site-health/site-health-accessibility.js +5 -103
  5. package/dist/components/admin/site-health/site-health-axe-core.js +4 -10
  6. package/dist/components/admin/site-health/site-health-cloudwatch.js +24 -26
  7. package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +4 -10
  8. package/dist/components/admin/site-health/site-health-github.js +9 -15
  9. package/dist/components/admin/site-health/site-health-google-analytics.integration.js +1 -107
  10. package/dist/components/admin/site-health/site-health-google-analytics.js +21 -29
  11. package/dist/components/admin/site-health/site-health-google-search-console.integration.js +1 -113
  12. package/dist/components/admin/site-health/site-health-google-search-console.js +22 -28
  13. package/dist/components/admin/site-health/site-health-on-site-seo.js +8 -82
  14. package/dist/components/admin/site-health/site-health-overview.js +5 -19
  15. package/dist/components/admin/site-health/site-health-performance.js +5 -167
  16. package/dist/components/admin/site-health/site-health-security.js +7 -148
  17. package/dist/components/admin/site-health/site-health-seo.js +5 -103
  18. package/dist/components/admin/site-health/site-health-template.js +68 -43
  19. package/dist/components/admin/site-health/site-health-uptime.js +4 -9
  20. package/dist/components/admin/site-health/site-health-utils.js +170 -0
  21. package/dist/components/admin/site-health/site-health.css +8 -9
  22. package/dist/index.adminclient.js +2 -5
  23. package/dist/index.adminserver.js +6 -4
  24. package/dist/index.js +39 -45
  25. package/dist/types/components/admin/site-health/google.api.integration.d.ts +82 -0
  26. package/dist/types/components/admin/site-health/google.api.integration.d.ts.map +1 -0
  27. package/dist/types/components/admin/site-health/google.api.utils.d.ts +32 -0
  28. package/dist/types/components/admin/site-health/google.api.utils.d.ts.map +1 -0
  29. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
  30. package/dist/types/components/admin/site-health/site-health-axe-core.d.ts.map +1 -1
  31. package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts.map +1 -1
  32. package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts.map +1 -1
  33. package/dist/types/components/admin/site-health/site-health-github.d.ts.map +1 -1
  34. package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts.map +1 -1
  35. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts +1 -21
  36. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts.map +1 -1
  37. package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts.map +1 -1
  38. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts +1 -41
  39. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts.map +1 -1
  40. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
  41. package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
  42. package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
  43. package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
  44. package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
  45. package/dist/types/components/admin/site-health/site-health-template.d.ts +8 -1
  46. package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -1
  47. package/dist/types/components/admin/site-health/site-health-uptime.d.ts.map +1 -1
  48. package/dist/types/components/admin/site-health/site-health-utils.d.ts +17 -0
  49. package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -0
  50. package/dist/types/index.adminclient.d.ts +2 -5
  51. package/dist/types/index.adminserver.d.ts +6 -4
  52. package/dist/types/index.d.ts +38 -44
  53. package/dist/types/stories/admin/site-health.stories.d.ts.map +1 -1
  54. package/dist/types/tests/google.api.integration.test.d.ts +2 -0
  55. package/dist/types/tests/google.api.integration.test.d.ts.map +1 -0
  56. package/dist/types/tests/google.api.utils.test.d.ts +5 -0
  57. package/dist/types/tests/google.api.utils.test.d.ts.map +1 -0
  58. package/package.json +1 -1
  59. package/dist/components/admin/site-health/google-api-auth.js +0 -69
  60. package/dist/types/components/admin/site-health/google-api-auth.d.ts +0 -37
  61. package/dist/types/components/admin/site-health/google-api-auth.d.ts.map +0 -1
@@ -1,22 +1,16 @@
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
- import { getScoreIndicator } from './site-health-indicators';
5
+ import { formatAuditItem, getAuditScoreIcon, getScoreColor, formatScore } from './site-health-utils';
7
6
  SiteHealthPerformance.propTypes = {
8
7
  siteName: PropTypes.string.isRequired,
9
8
  };
10
9
  export function SiteHealthPerformance({ siteName }) {
11
- const fetchCWVData = useCallback(async (site) => {
12
- const response = await fetch(`/api/site-health/core-web-vitals?siteName=${encodeURIComponent(site)}`);
13
- const result = await response.json();
14
- if (!result.success) {
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
  }
@@ -24,162 +18,6 @@ export function SiteHealthPerformance({ siteName }) {
24
18
  if (siteData.status === 'error') {
25
19
  return (_jsxs("p", { style: { color: '#ef4444', fontSize: '0.875rem' }, children: ["Error: ", siteData.error] }));
26
20
  }
27
- const getAuditScoreIcon = (score) => {
28
- return getScoreIndicator(score).icon;
29
- };
30
- const getScoreColor = (score) => {
31
- return getScoreIndicator(score).color;
32
- };
33
- const formatScore = (score) => {
34
- if (score === null)
35
- return 'N/A';
36
- return `${Math.round(score * 100)}%`;
37
- };
38
- // Helper function to display audit item details
39
- const formatAuditItem = (item, auditTitle) => {
40
- // Handle URLs
41
- if (item.url && typeof item.url === 'string') {
42
- return item.url;
43
- }
44
- // Handle sources (like JavaScript files)
45
- if (item.source && typeof item.source === 'string') {
46
- return item.source;
47
- }
48
- // Handle text descriptions
49
- if (item.text && typeof item.text === 'string') {
50
- return item.text;
51
- }
52
- // Handle entities (like "Google Tag Manager")
53
- if (item.entity && typeof item.entity === 'string') {
54
- return item.entity;
55
- }
56
- // Handle nodes with selectors
57
- if (item.node && typeof item.node === 'object' && 'selector' in item.node) {
58
- return `Element: ${item.node.selector}`;
59
- }
60
- // Handle nodes with snippets
61
- if (item.node && typeof item.node === 'object' && 'snippet' in item.node) {
62
- const snippet = item.node.snippet;
63
- return `Element: ${snippet.length > 50 ? snippet.substring(0, 50) + '...' : snippet}`;
64
- }
65
- // Handle origins (like domains)
66
- if (item.origin && typeof item.origin === 'string') {
67
- return item.origin;
68
- }
69
- // Handle labels
70
- if (item.label && typeof item.label === 'string') {
71
- return item.label;
72
- }
73
- // Handle numeric values with units
74
- if (item.value && typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
75
- const value = item.value;
76
- return `${value.value}${item.unit || ''}`;
77
- }
78
- // Handle statistics
79
- if (item.statistic && typeof item.statistic === 'string' && item.value) {
80
- if (typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
81
- const value = item.value;
82
- return `${item.statistic}: ${value.value}`;
83
- }
84
- return item.statistic;
85
- }
86
- // Handle timing data with audit context
87
- if (typeof item === 'number') {
88
- let context = '';
89
- if (auditTitle) {
90
- if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
91
- context = ' server response';
92
- }
93
- else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
94
- context = ' network request';
95
- }
96
- else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
97
- context = ' render blocking';
98
- }
99
- else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
100
- context = ' JavaScript';
101
- }
102
- else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
103
- context = ' media resource';
104
- }
105
- }
106
- return `${item.toFixed(2)}ms${context}`;
107
- }
108
- if (item.value && typeof item.value === 'number') {
109
- const unit = item.unit || 'ms';
110
- let context = '';
111
- if (auditTitle && unit === 'ms') {
112
- if (auditTitle.toLowerCase().includes('server')) {
113
- context = ' server time';
114
- }
115
- else if (auditTitle.toLowerCase().includes('network')) {
116
- context = ' network time';
117
- }
118
- }
119
- return `${item.value.toFixed(2)}${unit}${context}`;
120
- }
121
- // Handle timing data with more context
122
- if (item.duration && typeof item.duration === 'number') {
123
- const duration = item.duration;
124
- let context = '';
125
- if (item.url && typeof item.url === 'string') {
126
- context = ` for ${item.url}`;
127
- }
128
- else if (item.source && typeof item.source === 'string') {
129
- context = ` for ${item.source}`;
130
- }
131
- else if (item.name && typeof item.name === 'string') {
132
- context = ` for ${item.name}`;
133
- }
134
- else if (item.path && typeof item.path === 'string') {
135
- context = ` for ${item.path}`;
136
- }
137
- else if (item.request && typeof item.request === 'string') {
138
- context = ` for ${item.request}`;
139
- }
140
- return `${duration.toFixed(2)}ms${context}`;
141
- }
142
- // Handle response times
143
- if (item.responseTime && typeof item.responseTime === 'number') {
144
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
145
- return `${item.responseTime.toFixed(2)}ms response time${url}`;
146
- }
147
- // Handle start/end times
148
- if ((item.startTime || item.endTime) && typeof (item.startTime || item.endTime) === 'number') {
149
- const start = item.startTime && typeof item.startTime === 'number' ? item.startTime.toFixed(2) : '?';
150
- const end = item.endTime && typeof item.endTime === 'number' ? item.endTime.toFixed(2) : '?';
151
- const url = (item.url && typeof item.url === 'string') ? ` for ${item.url}` : '';
152
- return `${start}ms - ${end}ms${url}`;
153
- }
154
- // Handle transfer size with timing
155
- if (item.transferSize && typeof item.transferSize === 'number' && item.duration && typeof item.duration === 'number') {
156
- const size = (item.transferSize / 1024).toFixed(1);
157
- const time = item.duration.toFixed(2);
158
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
159
- return `${size} KB in ${time}ms${url}`;
160
- }
161
- // Handle main thread time
162
- if (item.mainThreadTime && typeof item.mainThreadTime === 'number') {
163
- return `${item.mainThreadTime.toFixed(1)}ms`;
164
- }
165
- // For other objects, try to find a meaningful display
166
- if (item.group && typeof item.group === 'string') {
167
- return item.group;
168
- }
169
- if (item.type && typeof item.type === 'string') {
170
- return item.type;
171
- }
172
- // If we can't find anything meaningful, provide a generic description
173
- // This handles raw timing data that might be from various performance metrics
174
- if (typeof item === 'number') {
175
- return `${item.toFixed(2)}ms`;
176
- }
177
- if (item.value && typeof item.value === 'number') {
178
- const unit = item.unit || 'ms';
179
- return `${item.value.toFixed(2)}${unit}`;
180
- }
181
- return 'Performance metric data available';
182
- };
183
21
  return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: siteData.site.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", siteData.url] }), _jsx("div", { style: { marginBottom: '1.5rem' }, children: _jsxs("div", { className: "health-score-item", style: { width: '100%' }, children: [_jsx("div", { className: "health-score-label", children: "Performance Score" }), _jsx("div", { className: "health-score-value", style: { color: getScoreColor(siteData.scores.performance) }, children: formatScore(siteData.scores.performance) }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
184
22
  width: siteData.scores.performance !== null ? `${siteData.scores.performance * 100}%` : '0%',
185
23
  backgroundColor: siteData.scores.performance !== null ? getScoreColor(siteData.scores.performance) : '#6b7280'
@@ -1,159 +1,18 @@
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
- import { getScoreIndicator } from './site-health-indicators';
5
+ import { formatAuditItem, getAuditScoreIcon, getScoreColor } from './site-health-utils';
7
6
  SiteHealthSecurity.propTypes = {
8
7
  siteName: PropTypes.string.isRequired,
9
8
  };
10
9
  export function SiteHealthSecurity({ siteName }) {
11
- const fetchSecurityData = useCallback(async (site) => {
12
- // Fetch PSI data for best practices security audits
13
- const psiResponse = await fetch(`/api/site-health/core-web-vitals?siteName=${encodeURIComponent(site)}`);
14
- const psiResult = await psiResponse.json();
15
- return {
16
- psiData: psiResult.success ? psiResult : undefined
17
- };
18
- }, []);
19
- return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Site Security", fetchData: fetchSecurityData, children: (data) => {
20
- const getScoreColor = (score) => {
21
- return getScoreIndicator(score).color;
22
- };
23
- const getAuditScoreIcon = (score) => {
24
- return getScoreIndicator(score).icon;
25
- };
26
- // Helper function to display audit item details
27
- const formatAuditItem = (item, auditTitle) => {
28
- // Handle URLs
29
- if (item.url && typeof item.url === 'string') {
30
- return item.url;
31
- }
32
- // Handle sources (like JavaScript files)
33
- if (item.source && typeof item.source === 'string') {
34
- return item.source;
35
- }
36
- // Handle text descriptions
37
- if (item.text && typeof item.text === 'string') {
38
- return item.text;
39
- }
40
- // Handle entities (like "Google Tag Manager")
41
- if (item.entity && typeof item.entity === 'string') {
42
- return item.entity;
43
- }
44
- // Handle nodes with selectors
45
- if (item.node && typeof item.node === 'object' && 'selector' in item.node) {
46
- return `Element: ${item.node.selector}`;
47
- }
48
- // Handle nodes with snippets
49
- if (item.node && typeof item.node === 'object' && 'snippet' in item.node) {
50
- const snippet = item.node.snippet;
51
- return `Element: ${snippet.length > 50 ? snippet.substring(0, 50) + '...' : snippet}`;
52
- }
53
- // Handle origins (like domains)
54
- if (item.origin && typeof item.origin === 'string') {
55
- return item.origin;
56
- }
57
- // Handle labels
58
- if (item.label && typeof item.label === 'string') {
59
- return item.label;
60
- }
61
- // Handle numeric values with units
62
- if (item.value && typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
63
- const value = item.value;
64
- return `${value.value}${item.unit || ''}`;
65
- }
66
- // Handle statistics
67
- if (item.statistic && typeof item.statistic === 'string' && item.value) {
68
- if (typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
69
- const value = item.value;
70
- return `${item.statistic}: ${value.value}`;
71
- }
72
- return item.statistic;
73
- }
74
- // Handle timing data with more context
75
- if (item.duration && typeof item.duration === 'number') {
76
- const duration = item.duration;
77
- let context = '';
78
- if (item.url && typeof item.url === 'string') {
79
- context = ` for ${item.url}`;
80
- }
81
- else if (item.source && typeof item.source === 'string') {
82
- context = ` for ${item.source}`;
83
- }
84
- else if (item.name && typeof item.name === 'string') {
85
- context = ` for ${item.name}`;
86
- }
87
- return `${duration.toFixed(2)}ms${context}`;
88
- }
89
- // Handle response times
90
- if (item.responseTime && typeof item.responseTime === 'number') {
91
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
92
- return `${item.responseTime.toFixed(2)}ms response time${url}`;
93
- }
94
- // Handle start/end times
95
- if ((item.startTime || item.endTime) && typeof (item.startTime || item.endTime) === 'number') {
96
- const start = item.startTime && typeof item.startTime === 'number' ? item.startTime.toFixed(2) : '?';
97
- const end = item.endTime && typeof item.endTime === 'number' ? item.endTime.toFixed(2) : '?';
98
- const url = (item.url && typeof item.url === 'string') ? ` for ${item.url}` : '';
99
- return `${start}ms - ${end}ms${url}`;
100
- }
101
- // Handle transfer size with timing
102
- if (item.transferSize && typeof item.transferSize === 'number' && item.duration && typeof item.duration === 'number') {
103
- const size = (item.transferSize / 1024).toFixed(1);
104
- const time = item.duration.toFixed(2);
105
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
106
- return `${size} KB in ${time}ms${url}`;
107
- }
108
- // Handle main thread time
109
- if (item.mainThreadTime && typeof item.mainThreadTime === 'number') {
110
- return `${item.mainThreadTime.toFixed(1)}ms`;
111
- }
112
- // For other objects, try to find a meaningful display
113
- if (item.group && typeof item.group === 'string') {
114
- return item.group;
115
- }
116
- if (item.type && typeof item.type === 'string') {
117
- return item.type;
118
- }
119
- // If we can't find anything meaningful, provide a generic description
120
- // This handles raw timing data that might be from various performance metrics
121
- if (typeof item === 'number') {
122
- let context = '';
123
- if (auditTitle) {
124
- if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
125
- context = ' server response';
126
- }
127
- else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
128
- context = ' network request';
129
- }
130
- else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
131
- context = ' render blocking';
132
- }
133
- else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
134
- context = ' JavaScript';
135
- }
136
- else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
137
- context = ' media resource';
138
- }
139
- }
140
- return `${item.toFixed(2)}ms${context}`;
141
- }
142
- if (item.value && typeof item.value === 'number') {
143
- const unit = item.unit || 'ms';
144
- let context = '';
145
- if (auditTitle && unit === 'ms') {
146
- if (auditTitle.toLowerCase().includes('server')) {
147
- context = ' server time';
148
- }
149
- else if (auditTitle.toLowerCase().includes('network')) {
150
- context = ' network time';
151
- }
152
- }
153
- return `${item.value.toFixed(2)}${unit}${context}`;
154
- }
155
- return 'Details available';
156
- };
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) => {
157
16
  const psiData = data?.psiData?.data?.[0];
158
17
  if (!psiData) {
159
18
  return (_jsx("p", { style: { color: '#6b7280' }, children: "No security data available for this site." }));
@@ -1,114 +1,16 @@
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
- import { getScoreIndicator } from './site-health-indicators';
5
+ import { formatAuditItem, getAuditScoreIcon, getScoreColor } from './site-health-utils';
7
6
  SiteHealthSEO.propTypes = {
8
7
  siteName: PropTypes.string.isRequired,
9
8
  };
10
9
  export function SiteHealthSEO({ siteName }) {
11
- const fetchSEOData = useCallback(async (site) => {
12
- const response = await fetch(`/api/site-health/core-web-vitals?siteName=${encodeURIComponent(site)}`);
13
- const result = await response.json();
14
- if (!result.success) {
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) => {
20
- const getScoreColor = (score) => {
21
- return getScoreIndicator(score).color;
22
- };
23
- const getAuditScoreIcon = (score) => {
24
- return getScoreIndicator(score).icon;
25
- };
26
- // Helper function to display audit item details
27
- const formatAuditItem = (item, auditTitle) => {
28
- // Handle URLs
29
- if (item.url && typeof item.url === 'string') {
30
- return item.url;
31
- }
32
- // Handle sources (like JavaScript files)
33
- if (item.source && typeof item.source === 'string') {
34
- return item.source;
35
- }
36
- // Handle text descriptions
37
- if (item.text && typeof item.text === 'string') {
38
- return item.text;
39
- }
40
- // Handle entities (like "Google Tag Manager")
41
- if (item.entity && typeof item.entity === 'string') {
42
- return item.entity;
43
- }
44
- // Handle nodes with selectors
45
- if (item.node && typeof item.node === 'object' && 'selector' in item.node) {
46
- return `Element: ${item.node.selector}`;
47
- }
48
- // Handle nodes with snippets
49
- if (item.node && typeof item.node === 'object' && 'snippet' in item.node) {
50
- const snippet = item.node.snippet;
51
- return `Element: ${snippet.length > 50 ? snippet.substring(0, 50) + '...' : snippet}`;
52
- }
53
- // Handle origins (like domains)
54
- if (item.origin && typeof item.origin === 'string') {
55
- return item.origin;
56
- }
57
- // Handle labels
58
- if (item.label && typeof item.label === 'string') {
59
- return item.label;
60
- }
61
- // Handle numeric values with units
62
- if (item.value && typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
63
- const value = item.value;
64
- return `${value.value}${item.unit || ''}`;
65
- }
66
- // Handle statistics
67
- if (item.statistic && typeof item.statistic === 'string' && item.value) {
68
- if (typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
69
- const value = item.value;
70
- return `${item.statistic}: ${value.value}`;
71
- }
72
- return item.statistic;
73
- }
74
- // Handle timing data with audit context
75
- if (typeof item === 'number') {
76
- let context = '';
77
- if (auditTitle) {
78
- if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
79
- context = ' server response';
80
- }
81
- else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
82
- context = ' network request';
83
- }
84
- else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
85
- context = ' render blocking';
86
- }
87
- else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
88
- context = ' JavaScript';
89
- }
90
- else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
91
- context = ' media resource';
92
- }
93
- }
94
- return `${item.toFixed(2)}ms${context}`;
95
- }
96
- if (item.value && typeof item.value === 'number') {
97
- const unit = item.unit || 'ms';
98
- let context = '';
99
- if (auditTitle && unit === 'ms') {
100
- if (auditTitle.toLowerCase().includes('server')) {
101
- context = ' server time';
102
- }
103
- else if (auditTitle.toLowerCase().includes('network')) {
104
- context = ' network time';
105
- }
106
- }
107
- return `${item.value.toFixed(2)}${unit}${context}`;
108
- }
109
- // If we can't find anything meaningful, show a generic message
110
- return 'Details available';
111
- };
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) => {
112
14
  if (!data?.data || data.data.length === 0) {
113
15
  return (_jsx("p", { style: { color: '#6b7280' }, children: "No SEO data available for this site." }));
114
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
- fetchData: PropTypes.func.isRequired,
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
- return () => {
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
- const fetchUptimeData = async (site) => {
10
- const response = await fetch(`/api/site-health/uptime?siteName=${encodeURIComponent(site)}`);
11
- const result = await response.json();
12
- if (!result.success) {
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
  }