@pixelated-tech/components 3.4.1 → 3.4.3
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.COMPONENTS.md +148 -0
- package/README.md +30 -10
- package/dist/components/admin/site-health/seo-metrics.config.json +111 -0
- package/dist/components/admin/site-health/site-health-cloudwatch.integration.js +142 -0
- package/dist/components/admin/site-health/site-health-cloudwatch.js +44 -0
- package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +297 -3
- package/dist/components/cms/pixelated.linkedin1.js +1 -0
- package/dist/index.js +1 -0
- package/dist/index.server.js +1 -0
- package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts +8 -0
- package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts.map +1 -0
- package/dist/types/components/admin/site-health/site-health-cloudwatch.integration.d.ts +25 -0
- package/dist/types/components/admin/site-health/site-health-cloudwatch.integration.d.ts.map +1 -0
- package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
- package/dist/types/components/cms/pixelated.linkedin1.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.server.d.ts +1 -0
- package/dist/types/stories/admin/site-health.stories.d.ts +4 -0
- package/dist/types/stories/admin/site-health.stories.d.ts.map +1 -1
- package/dist/types/tests/site-health-cloudwatch.test.d.ts +2 -0
- package/dist/types/tests/site-health-cloudwatch.test.d.ts.map +1 -0
- package/dist/types/tests/site-health-on-site-seo.integration.test.d.ts +2 -0
- package/dist/types/tests/site-health-on-site-seo.integration.test.d.ts.map +1 -0
- package/package.json +3 -2
package/README.COMPONENTS.md
CHANGED
|
@@ -2001,6 +2001,154 @@ import { Timeline } from '@pixelated-tech/components';
|
|
|
2001
2001
|
|
|
2002
2002
|
---
|
|
2003
2003
|
|
|
2004
|
+
## Admin Components
|
|
2005
|
+
|
|
2006
|
+
### Site Health
|
|
2007
|
+
|
|
2008
|
+
Comprehensive site monitoring and health check components for performance, security, and SEO analysis.
|
|
2009
|
+
|
|
2010
|
+
#### SiteHealthOverview
|
|
2011
|
+
|
|
2012
|
+
Displays Core Web Vitals and Lighthouse performance metrics.
|
|
2013
|
+
|
|
2014
|
+
```tsx
|
|
2015
|
+
import { SiteHealthOverview } from '@pixelated-tech/components';
|
|
2016
|
+
|
|
2017
|
+
<SiteHealthOverview siteName="my-site" />
|
|
2018
|
+
```
|
|
2019
|
+
|
|
2020
|
+
#### SiteHealthAxeCore
|
|
2021
|
+
|
|
2022
|
+
Accessibility auditing using axe-core.
|
|
2023
|
+
|
|
2024
|
+
```tsx
|
|
2025
|
+
import { SiteHealthAxeCore } from '@pixelated-tech/components';
|
|
2026
|
+
|
|
2027
|
+
<SiteHealthAxeCore siteName="my-site" />
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
#### SiteHealthPerformance
|
|
2031
|
+
|
|
2032
|
+
Performance monitoring and metrics.
|
|
2033
|
+
|
|
2034
|
+
```tsx
|
|
2035
|
+
import { SiteHealthPerformance } from '@pixelated-tech/components';
|
|
2036
|
+
|
|
2037
|
+
<SiteHealthPerformance siteName="my-site" />
|
|
2038
|
+
```
|
|
2039
|
+
|
|
2040
|
+
#### SiteHealthSecurity
|
|
2041
|
+
|
|
2042
|
+
Security scanning and vulnerability assessment.
|
|
2043
|
+
|
|
2044
|
+
```tsx
|
|
2045
|
+
import { SiteHealthSecurity } from '@pixelated-tech/components';
|
|
2046
|
+
|
|
2047
|
+
<SiteHealthSecurity siteName="my-site" />
|
|
2048
|
+
```
|
|
2049
|
+
|
|
2050
|
+
#### SiteHealthSEO
|
|
2051
|
+
|
|
2052
|
+
SEO analysis and recommendations.
|
|
2053
|
+
|
|
2054
|
+
```tsx
|
|
2055
|
+
import { SiteHealthSEO } from '@pixelated-tech/components';
|
|
2056
|
+
|
|
2057
|
+
<SiteHealthSEO siteName="my-site" />
|
|
2058
|
+
```
|
|
2059
|
+
|
|
2060
|
+
#### SiteHealthOnSiteSEO
|
|
2061
|
+
|
|
2062
|
+
Advanced on-page SEO analysis with Puppeteer rendering.
|
|
2063
|
+
|
|
2064
|
+
```tsx
|
|
2065
|
+
import { SiteHealthOnSiteSEO } from '@pixelated-tech/components';
|
|
2066
|
+
|
|
2067
|
+
<SiteHealthOnSiteSEO siteName="my-site" />
|
|
2068
|
+
```
|
|
2069
|
+
|
|
2070
|
+
#### SiteHealthCloudwatch
|
|
2071
|
+
|
|
2072
|
+
AWS CloudWatch uptime monitoring and availability tracking.
|
|
2073
|
+
|
|
2074
|
+
```tsx
|
|
2075
|
+
import { SiteHealthCloudwatch } from '@pixelated-tech/components';
|
|
2076
|
+
|
|
2077
|
+
<SiteHealthCloudwatch
|
|
2078
|
+
siteName="my-site"
|
|
2079
|
+
startDate="2024-01-01"
|
|
2080
|
+
endDate="2024-01-31"
|
|
2081
|
+
/>
|
|
2082
|
+
```
|
|
2083
|
+
|
|
2084
|
+
#### SiteHealthGoogleAnalytics
|
|
2085
|
+
|
|
2086
|
+
Google Analytics integration and reporting.
|
|
2087
|
+
|
|
2088
|
+
```tsx
|
|
2089
|
+
import { SiteHealthGoogleAnalytics } from '@pixelated-tech/components';
|
|
2090
|
+
|
|
2091
|
+
<SiteHealthGoogleAnalytics siteName="my-site" />
|
|
2092
|
+
```
|
|
2093
|
+
|
|
2094
|
+
#### SiteHealthGoogleSearchConsole
|
|
2095
|
+
|
|
2096
|
+
Google Search Console data and insights.
|
|
2097
|
+
|
|
2098
|
+
```tsx
|
|
2099
|
+
import { SiteHealthGoogleSearchConsole } from '@pixelated-tech/components';
|
|
2100
|
+
|
|
2101
|
+
<SiteHealthGoogleSearchConsole siteName="my-site" />
|
|
2102
|
+
```
|
|
2103
|
+
|
|
2104
|
+
#### SiteHealthDependencyVulnerabilities
|
|
2105
|
+
|
|
2106
|
+
NPM package vulnerability scanning.
|
|
2107
|
+
|
|
2108
|
+
```tsx
|
|
2109
|
+
import { SiteHealthDependencyVulnerabilities } from '@pixelated-tech/components';
|
|
2110
|
+
|
|
2111
|
+
<SiteHealthDependencyVulnerabilities siteName="my-site" />
|
|
2112
|
+
```
|
|
2113
|
+
|
|
2114
|
+
#### SiteHealthGit
|
|
2115
|
+
|
|
2116
|
+
Git repository health and commit analysis.
|
|
2117
|
+
|
|
2118
|
+
```tsx
|
|
2119
|
+
import { SiteHealthGit } from '@pixelated-tech/components';
|
|
2120
|
+
|
|
2121
|
+
<SiteHealthGit siteName="my-site" />
|
|
2122
|
+
```
|
|
2123
|
+
|
|
2124
|
+
#### SiteHealthUptime
|
|
2125
|
+
|
|
2126
|
+
Website uptime monitoring.
|
|
2127
|
+
|
|
2128
|
+
```tsx
|
|
2129
|
+
import { SiteHealthUptime } from '@pixelated-tech/components';
|
|
2130
|
+
|
|
2131
|
+
<SiteHealthUptime siteName="my-site" />
|
|
2132
|
+
```
|
|
2133
|
+
|
|
2134
|
+
### Sites
|
|
2135
|
+
|
|
2136
|
+
Site management and configuration components.
|
|
2137
|
+
|
|
2138
|
+
#### Sites Integration
|
|
2139
|
+
|
|
2140
|
+
```tsx
|
|
2141
|
+
import { loadSitesConfig, saveSitesConfig } from '@pixelated-tech/components';
|
|
2142
|
+
|
|
2143
|
+
// Load site configuration
|
|
2144
|
+
const sites = loadSitesConfig();
|
|
2145
|
+
|
|
2146
|
+
// Save site configuration
|
|
2147
|
+
saveSitesConfig(sites);
|
|
2148
|
+
```
|
|
2149
|
+
|
|
2150
|
+
---
|
|
2151
|
+
|
|
2004
2152
|
## Entertainment
|
|
2005
2153
|
|
|
2006
2154
|
### NerdJoke
|
package/README.md
CHANGED
|
@@ -165,6 +165,8 @@ User interface and interaction components:
|
|
|
165
165
|
- **Menu** - Navigation components (Simple, Accordion, Expando)
|
|
166
166
|
- **Tab** - Tabbed interface component for organizing content
|
|
167
167
|
- **Tiles** - Image grid and tile layouts
|
|
168
|
+
- **FontSelector** - Font selection and Google Fonts integration
|
|
169
|
+
- **CompoundFontSelector** - Advanced font selection with multiple font families
|
|
168
170
|
|
|
169
171
|
### Development Tools
|
|
170
172
|
Components for development, configuration, and site building:
|
|
@@ -172,6 +174,7 @@ Components for development, configuration, and site building:
|
|
|
172
174
|
- **ComponentSelector** - Component selection interface
|
|
173
175
|
- **ComponentTree** - Visual component hierarchy display
|
|
174
176
|
- **ConfigBuilder** - Interactive configuration builder for site settings, metadata, routes, and visual design tokens
|
|
177
|
+
- **ConfigEngine** - Configuration processing and validation engine
|
|
175
178
|
- **PageBuilderUI** - User interface for page building
|
|
176
179
|
- **SaveLoadSection** - Save and load functionality for configurations
|
|
177
180
|
|
|
@@ -213,6 +216,23 @@ External service integrations:
|
|
|
213
216
|
- **Yelp** - Business reviews and ratings
|
|
214
217
|
|
|
215
218
|
|
|
219
|
+
### Site Health & Monitoring
|
|
220
|
+
Comprehensive site health monitoring and analytics:
|
|
221
|
+
- **SiteHealthOverview** - Dashboard overview of site health metrics
|
|
222
|
+
- **SiteHealthPerformance** - Performance monitoring and optimization insights
|
|
223
|
+
- **SiteHealthAccessibility** - Accessibility compliance testing with axe-core
|
|
224
|
+
- **SiteHealthSecurity** - Security vulnerability scanning and recommendations
|
|
225
|
+
- **SiteHealthSEO** - On-page SEO analysis and scoring
|
|
226
|
+
- **SiteHealthOnSiteSEO** - Advanced on-page SEO metrics (browser caching, gzip compression, mobile-first indexing, etc.)
|
|
227
|
+
- **SiteHealthGoogleAnalytics** - Google Analytics data integration
|
|
228
|
+
- **SiteHealthGoogleSearchConsole** - Google Search Console integration
|
|
229
|
+
- **SiteHealthCloudwatch** - AWS CloudWatch uptime monitoring
|
|
230
|
+
- **SiteHealthGit** - Git repository health and status
|
|
231
|
+
- **SiteHealthUptime** - Uptime monitoring and alerts
|
|
232
|
+
- **SiteHealthAxeCore** - Automated accessibility testing
|
|
233
|
+
- **SiteHealthDependencyVulnerabilities** - Dependency security scanning
|
|
234
|
+
|
|
235
|
+
|
|
216
236
|
## 🎨 Visual Design Configuration
|
|
217
237
|
|
|
218
238
|
The ConfigBuilder now includes a **Visual Design** tab that allows users to configure visual design tokens such as colors, fonts, spacing, and other design system variables. These tokens are stored in the `routes.json` file under the `visualdesign` object and can be used throughout your application for consistent theming.
|
|
@@ -302,7 +322,6 @@ npm run storybook
|
|
|
302
322
|
- [ ] **Project Scaffolding CLI**: Interactive CLI tool that generates complete Next.js projects with pixelated-components pre-configured, including routes.json, layout.tsx, package.json, and basic page structure
|
|
303
323
|
- [ ] **Template Marketplace**: Pre-built industry-specific templates (restaurant, law firm, contractor, etc.) that users can clone and customize
|
|
304
324
|
- [ ] **Configuration Wizard**: Step-by-step setup wizard that collects business info, generates site configuration, and creates initial content structure
|
|
305
|
-
- [ IP ] **Site Health Monitoring**: Automated monitoring dashboard that checks site performance, broken links, SEO scores, and security vulnerabilities across all sites
|
|
306
325
|
- [ ] **Content Migration Tools**: Automated importers for WordPress, Squarespace, Wix, and other platforms to migrate content to pixelated sites
|
|
307
326
|
- [ ] **A/B Testing Framework**: Built-in experimentation system for testing different layouts, content, and CTAs with automatic winner selection
|
|
308
327
|
- [ ] **Personalization Engine**: Dynamic content delivery based on user behavior, location, and preferences
|
|
@@ -381,16 +400,16 @@ Project Link: [https://github.com/brianwhaley/pixelated-components](https://gith
|
|
|
381
400
|
|
|
382
401
|
### Overview
|
|
383
402
|
|
|
384
|
-
**Current Status**: ✅ 2,
|
|
403
|
+
**Current Status**: ✅ 2,387 tests passing across 79 test files
|
|
385
404
|
|
|
386
405
|
| Metric | Value |
|
|
387
406
|
|--------|-------|
|
|
388
|
-
| Test Files |
|
|
389
|
-
| Total Tests | 2,
|
|
390
|
-
| Coverage (Statements) |
|
|
391
|
-
| Coverage (Lines) | 79.
|
|
392
|
-
| Coverage (Functions) |
|
|
393
|
-
| Coverage (Branches) | 67.
|
|
407
|
+
| Test Files | 79 |
|
|
408
|
+
| Total Tests | 2,387 |
|
|
409
|
+
| Coverage (Statements) | 77.13% |
|
|
410
|
+
| Coverage (Lines) | 79.73% |
|
|
411
|
+
| Coverage (Functions) | 77.98% |
|
|
412
|
+
| Coverage (Branches) | 67.55% |
|
|
394
413
|
| Test Framework | Vitest 4.x |
|
|
395
414
|
| Testing Library | @testing-library/react + jsdom |
|
|
396
415
|
|
|
@@ -429,8 +448,8 @@ npm run test:run # Single run (for CI)
|
|
|
429
448
|
- **buzzwordbingo.tsx**: 100% statements
|
|
430
449
|
- **markdown.tsx**: 100% statements
|
|
431
450
|
- **timeline.tsx**: 100% statements
|
|
451
|
+
- **config.client.tsx**: 100% statements
|
|
432
452
|
- **sidepanel.tsx**: 97.5% statements
|
|
433
|
-
- **config.server.tsx**: 50% statements
|
|
434
453
|
- **config.ts**: 96.55% statements
|
|
435
454
|
- **google.reviews.components.tsx**: 95.83% statements
|
|
436
455
|
- **schema-blogposting.tsx**: 95.24% statements
|
|
@@ -440,7 +459,7 @@ npm run test:run # Single run (for CI)
|
|
|
440
459
|
- **css.tsx**: 91.43% statements
|
|
441
460
|
- **functions.ts**: 90.91% statements
|
|
442
461
|
- **menu-expando.tsx**: 90.12% statements
|
|
443
|
-
- **
|
|
462
|
+
- **site-health-cloudwatch.tsx**: 88% statements
|
|
444
463
|
- **loading.tsx**: 85.71% statements
|
|
445
464
|
- **SaveLoadSection.tsx**: 84.85% statements
|
|
446
465
|
- **table.tsx**: 84.48% statements
|
|
@@ -461,6 +480,7 @@ npm run test:run # Single run (for CI)
|
|
|
461
480
|
- **componentMap.tsx**: 60% statements
|
|
462
481
|
- **propTypeIntrospection.tsx**: 60% statements
|
|
463
482
|
- **wordpress.functions.ts**: 51.43% statements
|
|
483
|
+
- **config.server.tsx**: 50% statements
|
|
464
484
|
- **PageEngine.tsx**: 48% statements
|
|
465
485
|
- **componentGeneration.tsx**: 38.89% statements
|
|
466
486
|
- **socialcard.tsx**: 29.51% statements
|
|
@@ -187,6 +187,101 @@
|
|
|
187
187
|
"countLogic": "count",
|
|
188
188
|
"scoreLogic": "present",
|
|
189
189
|
"displayTemplate": "{{count}} ItemProp tag(s) found"
|
|
190
|
+
},
|
|
191
|
+
"robots-meta-directives": {
|
|
192
|
+
"id": "robots-meta-directives",
|
|
193
|
+
"title": "Robots Meta Directives",
|
|
194
|
+
"description": "Checks for robots meta tags (noindex, nofollow, noarchive)",
|
|
195
|
+
"scoreDisplayMode": "binary",
|
|
196
|
+
"pattern": "<meta[^>]*name=[\"']robots[\"'][^>]*content=[\"'][^\"']*[\"'][^>]*>",
|
|
197
|
+
"countLogic": "count",
|
|
198
|
+
"scoreLogic": "present",
|
|
199
|
+
"displayTemplate": "{{count}} robots meta tag(s) found"
|
|
200
|
+
},
|
|
201
|
+
"hreflang-tags": {
|
|
202
|
+
"id": "hreflang-tags",
|
|
203
|
+
"title": "Hreflang Tags",
|
|
204
|
+
"description": "Checks for hreflang link tags for international SEO",
|
|
205
|
+
"scoreDisplayMode": "binary",
|
|
206
|
+
"pattern": "<link[^>]*rel=[\"']alternate[\"'][^>]*hreflang=[\"'][^\"']*[\"'][^>]*>",
|
|
207
|
+
"countLogic": "count",
|
|
208
|
+
"scoreLogic": "present",
|
|
209
|
+
"displayTemplate": "{{count}} hreflang link(s) found"
|
|
210
|
+
},
|
|
211
|
+
"indexability-status": {
|
|
212
|
+
"id": "indexability-status",
|
|
213
|
+
"title": "Indexability Status",
|
|
214
|
+
"description": "Checks for unintended noindex directives",
|
|
215
|
+
"scoreDisplayMode": "binary",
|
|
216
|
+
"pattern": "<meta[^>]*name=[\"']?robots[\"']?[^>]*content=[\"']?[^\"']*noindex[^\"']*[\"']?[^>]*>",
|
|
217
|
+
"countLogic": "count",
|
|
218
|
+
"scoreLogic": "exact",
|
|
219
|
+
"expectedCount": 0,
|
|
220
|
+
"displayTemplate": "{{count}} noindex directive(s) found"
|
|
221
|
+
},
|
|
222
|
+
"image-optimization": {
|
|
223
|
+
"id": "image-optimization",
|
|
224
|
+
"title": "Image Optimization",
|
|
225
|
+
"description": "Checks for lazy loading and modern image formats",
|
|
226
|
+
"scoreDisplayMode": "binary",
|
|
227
|
+
"pattern": "<img[^>]*loading=[\"']lazy[\"'][^>]*>|<img[^>]*\\.(webp|avif)[^>]*>",
|
|
228
|
+
"countLogic": "count",
|
|
229
|
+
"scoreLogic": "present",
|
|
230
|
+
"displayTemplate": "{{count}} optimized image(s) found"
|
|
231
|
+
},
|
|
232
|
+
"pagination-structure": {
|
|
233
|
+
"id": "pagination-structure",
|
|
234
|
+
"title": "Pagination Structure",
|
|
235
|
+
"description": "Checks for proper rel='next/prev' pagination links",
|
|
236
|
+
"scoreDisplayMode": "binary",
|
|
237
|
+
"pattern": "<link[^>]*rel=[\"']?(next|prev)[\"']?[^>]*href=[\"']?[^\"']*[\"']?[^>]*>",
|
|
238
|
+
"countLogic": "count",
|
|
239
|
+
"scoreLogic": "present",
|
|
240
|
+
"displayTemplate": "{{count}} pagination link(s) found"
|
|
241
|
+
},
|
|
242
|
+
"local-seo-elements": {
|
|
243
|
+
"id": "local-seo-elements",
|
|
244
|
+
"title": "Local SEO Elements",
|
|
245
|
+
"description": "Checks for local business schema markup",
|
|
246
|
+
"scoreDisplayMode": "binary",
|
|
247
|
+
"pattern": "(<script[^>]*type=[\"']application/ld\\+json[\"'][^>]*>[^<]*\"@type\"[^<]*\"LocalBusiness\"[^<]*</script>)|(<[^>]*itemtype=[\"'][^\"']*LocalBusiness[^\"']*[\"'][^>]*>)",
|
|
248
|
+
"countLogic": "count",
|
|
249
|
+
"scoreLogic": "present",
|
|
250
|
+
"displayTemplate": "{{count}} local business schema element(s) found"
|
|
251
|
+
},
|
|
252
|
+
"amp-validation": {
|
|
253
|
+
"id": "amp-validation",
|
|
254
|
+
"title": "AMP Validation",
|
|
255
|
+
"description": "Checks for Accelerated Mobile Pages implementation",
|
|
256
|
+
"scoreDisplayMode": "binary",
|
|
257
|
+
"pattern": "<html[^>]*amp[^>]*>|<script[^>]*src=[\"']?[^\"']*cdn\\.ampproject\\.org[^\"']*[\"']?[^>]*>",
|
|
258
|
+
"countLogic": "count",
|
|
259
|
+
"scoreLogic": "present",
|
|
260
|
+
"displayTemplate": "{{count}} AMP element(s) found"
|
|
261
|
+
},
|
|
262
|
+
"mobile-first-indexing": {
|
|
263
|
+
"id": "mobile-first-indexing",
|
|
264
|
+
"title": "Mobile-First Indexing",
|
|
265
|
+
"description": "Checks for mobile-friendly viewport and responsive design",
|
|
266
|
+
"scoreDisplayMode": "binary",
|
|
267
|
+
"dataCollector": "collectMobileFirstIndexingData",
|
|
268
|
+
"scorer": "calculateMobileFirstIndexingScore"
|
|
269
|
+
},
|
|
270
|
+
"international-seo": {
|
|
271
|
+
"id": "international-seo",
|
|
272
|
+
"title": "International SEO",
|
|
273
|
+
"description": "Checks for currency and locale detection elements",
|
|
274
|
+
"scoreDisplayMode": "binary",
|
|
275
|
+
"dataCollector": "collectInternationalSEOData",
|
|
276
|
+
"scorer": "calculateInternationalSEOData"
|
|
277
|
+
},
|
|
278
|
+
"faceted-navigation": {
|
|
279
|
+
"id": "faceted-navigation",
|
|
280
|
+
"title": "Faceted Navigation",
|
|
281
|
+
"description": "Checks for SEO-friendly filter and navigation URLs",
|
|
282
|
+
"scoreDisplayMode": "numeric",
|
|
283
|
+
"dataCollector": "collectFacetedNavigationData",
|
|
284
|
+
"scorer": "calculateFacetedNavigationScore"
|
|
190
285
|
}
|
|
191
286
|
}
|
|
192
287
|
},
|
|
@@ -258,6 +353,22 @@
|
|
|
258
353
|
"scoreDisplayMode": "binary",
|
|
259
354
|
"dataCollector": null,
|
|
260
355
|
"scorer": null
|
|
356
|
+
},
|
|
357
|
+
"gzip-compression": {
|
|
358
|
+
"id": "gzip-compression",
|
|
359
|
+
"title": "Gzip Compression",
|
|
360
|
+
"description": "Checks for server compression verification (requires HTTP headers analysis)",
|
|
361
|
+
"scoreDisplayMode": "binary",
|
|
362
|
+
"dataCollector": "collectGzipCompressionData",
|
|
363
|
+
"scorer": "calculateGzipCompressionScore"
|
|
364
|
+
},
|
|
365
|
+
"browser-caching": {
|
|
366
|
+
"id": "browser-caching",
|
|
367
|
+
"title": "Browser Caching",
|
|
368
|
+
"description": "Checks cache headers for static assets (requires HTTP headers analysis)",
|
|
369
|
+
"scoreDisplayMode": "binary",
|
|
370
|
+
"dataCollector": "collectBrowserCachingData",
|
|
371
|
+
"scorer": "calculateBrowserCachingScore"
|
|
261
372
|
}
|
|
262
373
|
}
|
|
263
374
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudWatch Health Check Integration Services
|
|
3
|
+
* Server-side utilities for Route53 health check data retrieval via CloudWatch
|
|
4
|
+
*/
|
|
5
|
+
"use server";
|
|
6
|
+
import { CloudWatchClient, GetMetricDataCommand } from '@aws-sdk/client-cloudwatch';
|
|
7
|
+
import { RouteCache } from './site-health-cache';
|
|
8
|
+
// Cache for health check data (15 minutes)
|
|
9
|
+
const healthCheckCache = new RouteCache(15 * 60 * 1000);
|
|
10
|
+
/**
|
|
11
|
+
* Get health check data for a site using CloudWatch metrics
|
|
12
|
+
*/
|
|
13
|
+
export async function getCloudwatchHealthCheckData(config, siteName, startDate, endDate) {
|
|
14
|
+
try {
|
|
15
|
+
// Check cache first
|
|
16
|
+
const cacheKey = `cloudwatch-${siteName}-${config.healthCheckId}-${startDate || 'default'}-${endDate || 'default'}`;
|
|
17
|
+
const cached = healthCheckCache.get(cacheKey);
|
|
18
|
+
if (cached) {
|
|
19
|
+
return { success: true, data: cached };
|
|
20
|
+
}
|
|
21
|
+
// Use CloudWatch to get historical health check data
|
|
22
|
+
const cloudWatchClient = new CloudWatchClient({
|
|
23
|
+
region: config.region || 'us-east-1'
|
|
24
|
+
});
|
|
25
|
+
// Set up date range
|
|
26
|
+
const endTime = endDate ? new Date(endDate) : new Date();
|
|
27
|
+
const startTime = startDate ? new Date(startDate) : new Date(endTime.getTime() - (30 * 24 * 60 * 60 * 1000)); // 30 days ago
|
|
28
|
+
// Add 1 day to end time to include the full end date
|
|
29
|
+
const endTimePlusOne = new Date(endTime);
|
|
30
|
+
endTimePlusOne.setDate(endTimePlusOne.getDate() + 1);
|
|
31
|
+
console.log(`CloudWatch: Fetching data for health check ${config.healthCheckId} from ${startTime.toISOString()} to ${endTimePlusOne.toISOString()}`);
|
|
32
|
+
const metricDataQuery = {
|
|
33
|
+
MetricDataQueries: [
|
|
34
|
+
{
|
|
35
|
+
Id: 'healthCheckStatus',
|
|
36
|
+
MetricStat: {
|
|
37
|
+
Metric: {
|
|
38
|
+
Namespace: 'AWS/Route53',
|
|
39
|
+
MetricName: 'HealthCheckStatus',
|
|
40
|
+
Dimensions: [
|
|
41
|
+
{
|
|
42
|
+
Name: 'HealthCheckId',
|
|
43
|
+
Value: config.healthCheckId
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
Period: 3600, // 1 hour intervals
|
|
48
|
+
Stat: 'Average'
|
|
49
|
+
},
|
|
50
|
+
ReturnData: true
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
StartTime: startTime,
|
|
54
|
+
EndTime: endTimePlusOne
|
|
55
|
+
};
|
|
56
|
+
const command = new GetMetricDataCommand(metricDataQuery);
|
|
57
|
+
const response = await cloudWatchClient.send(command);
|
|
58
|
+
console.log(`CloudWatch: Received ${response.MetricDataResults?.[0]?.Timestamps?.length || 0} data points`);
|
|
59
|
+
if (!response.MetricDataResults || response.MetricDataResults.length === 0) {
|
|
60
|
+
console.log(`CloudWatch: No metric data found for health check ${config.healthCheckId}`);
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: 'No health check metric data found'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const metricResult = response.MetricDataResults[0];
|
|
67
|
+
if (!metricResult.Timestamps || !metricResult.Values) {
|
|
68
|
+
console.log(`CloudWatch: No timestamps or values in metric data`);
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: 'No health check metric data available'
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Group data by date
|
|
75
|
+
const dateGroups = {};
|
|
76
|
+
metricResult.Timestamps.forEach((timestamp, index) => {
|
|
77
|
+
const date = timestamp.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
78
|
+
const value = metricResult.Values[index];
|
|
79
|
+
if (!dateGroups[date]) {
|
|
80
|
+
dateGroups[date] = { success: 0, failure: 0 };
|
|
81
|
+
}
|
|
82
|
+
// CloudWatch returns 1 for healthy, 0 for unhealthy
|
|
83
|
+
if (value >= 0.5) { // Consider >= 0.5 as success
|
|
84
|
+
dateGroups[date].success++;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
dateGroups[date].failure++;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Convert to data points
|
|
91
|
+
const data = Object.entries(dateGroups)
|
|
92
|
+
.map(([date, counts]) => {
|
|
93
|
+
const total = counts.success + counts.failure;
|
|
94
|
+
const successRate = total > 0 ? (counts.success / total) * 100 : 0;
|
|
95
|
+
return {
|
|
96
|
+
date,
|
|
97
|
+
successCount: counts.success,
|
|
98
|
+
failureCount: counts.failure,
|
|
99
|
+
totalChecks: total,
|
|
100
|
+
successRate: Math.round(successRate * 100) / 100 // Round to 2 decimal places
|
|
101
|
+
};
|
|
102
|
+
})
|
|
103
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
104
|
+
console.log(`CloudWatch: Processed ${data.length} date groups with data`);
|
|
105
|
+
// Fill in the date range with data points for each day
|
|
106
|
+
let filledData = [];
|
|
107
|
+
if (startDate && endDate) {
|
|
108
|
+
const start = new Date(startDate);
|
|
109
|
+
const end = new Date(endDate);
|
|
110
|
+
const dataMap = new Map(data.map(d => [d.date, d]));
|
|
111
|
+
for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) {
|
|
112
|
+
const dateStr = date.toISOString().split('T')[0];
|
|
113
|
+
const existingData = dataMap.get(dateStr);
|
|
114
|
+
if (existingData) {
|
|
115
|
+
filledData.push(existingData);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
filledData.push({
|
|
119
|
+
date: dateStr,
|
|
120
|
+
successCount: 0,
|
|
121
|
+
failureCount: 0,
|
|
122
|
+
totalChecks: 0,
|
|
123
|
+
successRate: 0
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
filledData = data;
|
|
130
|
+
}
|
|
131
|
+
// Cache the result
|
|
132
|
+
healthCheckCache.set(cacheKey, filledData);
|
|
133
|
+
return { success: true, data: filledData };
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error('CloudWatch error:', error);
|
|
137
|
+
return {
|
|
138
|
+
success: false,
|
|
139
|
+
error: error instanceof Error ? error.message : 'Failed to fetch health check data from CloudWatch'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ComposedChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
|
4
|
+
import { SiteHealthTemplate } from './site-health-template';
|
|
5
|
+
export function SiteHealthCloudwatch({ siteName, startDate, endDate }) {
|
|
6
|
+
const fetchCloudwatchData = async (site) => {
|
|
7
|
+
const params = new URLSearchParams({ siteName: site });
|
|
8
|
+
if (startDate)
|
|
9
|
+
params.append('startDate', startDate);
|
|
10
|
+
if (endDate)
|
|
11
|
+
params.append('endDate', endDate);
|
|
12
|
+
const response = await fetch(`/api/site-health/cloudwatch?${params.toString()}`);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`Failed to fetch CloudWatch data: ${response.status}`);
|
|
15
|
+
}
|
|
16
|
+
const result = await response.json();
|
|
17
|
+
if (!result.success) {
|
|
18
|
+
if (result.error?.includes('Health Check ID not configured')) {
|
|
19
|
+
throw new Error('Route53 Health Check ID not configured for this site');
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
throw new Error(result.error || 'Failed to load CloudWatch health check data');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return result.data;
|
|
26
|
+
};
|
|
27
|
+
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "CloudWatch Uptime", columnSpan: 2, fetchData: fetchCloudwatchData, children: (data) => {
|
|
28
|
+
if (!data || data.length === 0) {
|
|
29
|
+
return (_jsx("div", { className: "flex items-center justify-center h-64", children: _jsx("div", { className: "text-gray-500", children: "No uptime data available. Route53 health checks may not be configured to send metrics to CloudWatch." }) }));
|
|
30
|
+
}
|
|
31
|
+
// Check if all data points have zero checks (no actual data)
|
|
32
|
+
const hasActualData = data.some(point => point.totalChecks > 0);
|
|
33
|
+
if (!hasActualData) {
|
|
34
|
+
return (_jsx("div", { className: "flex items-center justify-center h-64", children: _jsxs("div", { className: "text-gray-500", children: ["Health check exists but has no metric data in CloudWatch for the selected period.", _jsx("br", {}), "Route53 health checks must be configured to send metrics to CloudWatch for historical data."] }) }));
|
|
35
|
+
}
|
|
36
|
+
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: data, 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: "CloudWatch Health Check Availability Over Time" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 }, label: { value: 'Check Count', angle: -90, position: 'insideLeft' } }), _jsx(Tooltip, { formatter: (value, name) => [
|
|
37
|
+
value?.toLocaleString() || '0',
|
|
38
|
+
name || 'Unknown'
|
|
39
|
+
], labelFormatter: (label) => `Date: ${label}` }), _jsx(Legend, { wrapperStyle: {
|
|
40
|
+
fontSize: '12px',
|
|
41
|
+
paddingTop: '10px'
|
|
42
|
+
} }), _jsx(Bar, { dataKey: "successCount", stackId: "checks", fill: "#10b981", name: "Successful Checks", radius: [2, 2, 0, 0] }), _jsx(Bar, { dataKey: "failureCount", stackId: "checks", fill: "#ef4444", name: "Failed Checks", radius: [2, 2, 0, 0] })] }, `cloudwatch-chart-${data.length}`) }) }) }));
|
|
43
|
+
} }));
|
|
44
|
+
}
|
|
@@ -19,7 +19,12 @@ const dataCollectors = {
|
|
|
19
19
|
collectSemanticTagsData,
|
|
20
20
|
collectTitleTagsData,
|
|
21
21
|
collectMetaKeywordsData,
|
|
22
|
-
collectMetaDescriptionsData
|
|
22
|
+
collectMetaDescriptionsData,
|
|
23
|
+
collectMobileFirstIndexingData,
|
|
24
|
+
collectInternationalSEOData,
|
|
25
|
+
collectFacetedNavigationData,
|
|
26
|
+
collectBrowserCachingData,
|
|
27
|
+
collectGzipCompressionData
|
|
23
28
|
};
|
|
24
29
|
/**
|
|
25
30
|
* Registry of scoring functions
|
|
@@ -28,7 +33,12 @@ const scorers = {
|
|
|
28
33
|
calculateSemanticTagsScore,
|
|
29
34
|
calculateTitleTagsScore,
|
|
30
35
|
calculateMetaKeywordsScore,
|
|
31
|
-
calculateMetaDescriptionsScore
|
|
36
|
+
calculateMetaDescriptionsScore,
|
|
37
|
+
calculateMobileFirstIndexingScore,
|
|
38
|
+
calculateInternationalSEOData,
|
|
39
|
+
calculateFacetedNavigationScore,
|
|
40
|
+
calculateBrowserCachingScore,
|
|
41
|
+
calculateGzipCompressionScore
|
|
32
42
|
};
|
|
33
43
|
/**
|
|
34
44
|
* Data collection functions
|
|
@@ -229,6 +239,262 @@ function calculateMetaDescriptionsScore(descriptionData) {
|
|
|
229
239
|
}
|
|
230
240
|
};
|
|
231
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Mobile-First Indexing Data Collector
|
|
244
|
+
*/
|
|
245
|
+
function collectMobileFirstIndexingData(html) {
|
|
246
|
+
const hasViewport = /<meta[^>]*name=["']viewport["'][^>]*content=["'][^"']*["'][^>]*>/i.test(html);
|
|
247
|
+
const hasResponsiveMeta = /<meta[^>]*name=["']viewport["'][^>]*content=["'][^"']*width=device-width[^"']*["'][^>]*>/i.test(html);
|
|
248
|
+
const hasMobileStyles = /@media[^}]*max-width[^}]*mobile|phone|tablet/i.test(html) || /<link[^>]*media=["'][^"']*handheld[^"']*["'][^>]*>/i.test(html);
|
|
249
|
+
return {
|
|
250
|
+
hasViewport,
|
|
251
|
+
hasResponsiveMeta,
|
|
252
|
+
hasMobileStyles,
|
|
253
|
+
score: (hasViewport && hasResponsiveMeta) ? 1 : 0
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Mobile-First Indexing Scorer
|
|
258
|
+
*/
|
|
259
|
+
function calculateMobileFirstIndexingScore(data) {
|
|
260
|
+
const score = data.score;
|
|
261
|
+
const displayValue = score ? 'Mobile-friendly viewport detected' : 'Missing or inadequate viewport configuration';
|
|
262
|
+
return {
|
|
263
|
+
score,
|
|
264
|
+
displayValue,
|
|
265
|
+
details: {
|
|
266
|
+
items: [
|
|
267
|
+
{ type: 'viewport', present: data.hasViewport, optimal: data.hasResponsiveMeta },
|
|
268
|
+
{ type: 'responsive-styles', present: data.hasMobileStyles }
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* International SEO Data Collector
|
|
275
|
+
*/
|
|
276
|
+
function collectInternationalSEOData(html) {
|
|
277
|
+
const currencySymbols = html.match(/[£€¥$₹₽₩₦₨₪₫₡₵₺₴₸₼₲₱₭₯₰₳₶₷₹₻₽₾₿]/g) || [];
|
|
278
|
+
const langAttributes = html.match(/lang=["'][^"']*["']/gi) || [];
|
|
279
|
+
const localeIndicators = html.match(/(en-US|en-GB|fr-FR|de-DE|es-ES|it-IT|pt-BR|ja-JP|ko-KR|zh-CN|zh-TW|ru-RU|ar-SA|hi-IN)/gi) || [];
|
|
280
|
+
return {
|
|
281
|
+
currencyCount: currencySymbols.length,
|
|
282
|
+
langAttributes: langAttributes.length,
|
|
283
|
+
localeIndicators: localeIndicators.length,
|
|
284
|
+
hasInternationalElements: currencySymbols.length > 0 || langAttributes.length > 0 || localeIndicators.length > 0
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* International SEO Scorer
|
|
289
|
+
*/
|
|
290
|
+
function calculateInternationalSEOData(data) {
|
|
291
|
+
const score = data.hasInternationalElements ? 1 : 0;
|
|
292
|
+
const displayValue = score ? `${data.currencyCount} currencies, ${data.langAttributes} lang attributes, ${data.localeIndicators} locale indicators found` : 'No international SEO elements detected';
|
|
293
|
+
return {
|
|
294
|
+
score,
|
|
295
|
+
displayValue,
|
|
296
|
+
details: {
|
|
297
|
+
items: [
|
|
298
|
+
{ type: 'currencies', count: data.currencyCount },
|
|
299
|
+
{ type: 'lang-attributes', count: data.langAttributes },
|
|
300
|
+
{ type: 'locale-indicators', count: data.localeIndicators }
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Faceted Navigation Data Collector
|
|
307
|
+
*/
|
|
308
|
+
function collectFacetedNavigationData(html) {
|
|
309
|
+
// Look for URL patterns that suggest faceted navigation (query parameters, filters)
|
|
310
|
+
const filterUrls = html.match(/href=["'][^"']*[?&][^"']*filter[^"']*["']/gi) || [];
|
|
311
|
+
const sortUrls = html.match(/href=["'][^"']*[?&][^"']*sort[^"']*["']/gi) || [];
|
|
312
|
+
const categoryUrls = html.match(/href=["'][^"']*[?&][^"']*category[^"']*["']/gi) || [];
|
|
313
|
+
const cleanUrls = html.match(/href=["'][^"']*\/[^?]*\/[^?]*["']/gi) || [];
|
|
314
|
+
return {
|
|
315
|
+
filterUrls: filterUrls.length,
|
|
316
|
+
sortUrls: sortUrls.length,
|
|
317
|
+
categoryUrls: categoryUrls.length,
|
|
318
|
+
cleanUrls: cleanUrls.length,
|
|
319
|
+
hasFacetedNavigation: (filterUrls.length + sortUrls.length + categoryUrls.length) > 0
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Faceted Navigation Scorer
|
|
324
|
+
*/
|
|
325
|
+
function calculateFacetedNavigationScore(data) {
|
|
326
|
+
const totalFaceted = data.filterUrls + data.sortUrls + data.categoryUrls;
|
|
327
|
+
const totalUrls = data.cleanUrls + totalFaceted;
|
|
328
|
+
// Calculate score as percentage of clean URLs vs total URLs
|
|
329
|
+
// Higher score = more clean URLs (better for SEO)
|
|
330
|
+
let score = 1; // Default to 100% if no URLs found
|
|
331
|
+
let displayValue = 'No URLs detected';
|
|
332
|
+
if (totalUrls > 0) {
|
|
333
|
+
score = data.cleanUrls / totalUrls; // Ratio of clean URLs to total URLs
|
|
334
|
+
const percentage = Math.round(score * 100);
|
|
335
|
+
if (totalFaceted === 0) {
|
|
336
|
+
displayValue = `100% clean URLs - excellent for SEO`;
|
|
337
|
+
}
|
|
338
|
+
else if (score >= 0.8) {
|
|
339
|
+
displayValue = `${percentage}% clean URLs (${data.cleanUrls} clean, ${totalFaceted} faceted)`;
|
|
340
|
+
}
|
|
341
|
+
else if (score >= 0.5) {
|
|
342
|
+
displayValue = `${percentage}% clean URLs - consider reducing faceted navigation (${data.cleanUrls} clean, ${totalFaceted} faceted)`;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
displayValue = `${percentage}% clean URLs - high faceted navigation may hurt SEO (${data.cleanUrls} clean, ${totalFaceted} faceted)`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
score,
|
|
350
|
+
displayValue,
|
|
351
|
+
details: {
|
|
352
|
+
items: [
|
|
353
|
+
{ type: 'clean-urls', count: data.cleanUrls },
|
|
354
|
+
{ type: 'filter-urls', count: data.filterUrls },
|
|
355
|
+
{ type: 'sort-urls', count: data.sortUrls },
|
|
356
|
+
{ type: 'category-urls', count: data.categoryUrls }
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Browser Caching Data Collector
|
|
363
|
+
*/
|
|
364
|
+
function collectBrowserCachingData(response) {
|
|
365
|
+
// Get response headers from Puppeteer response object
|
|
366
|
+
const headers = {};
|
|
367
|
+
try {
|
|
368
|
+
const rawHeaders = response.headers();
|
|
369
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
370
|
+
headers[key.toLowerCase()] = value;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
console.warn('Failed to get response headers:', error);
|
|
375
|
+
}
|
|
376
|
+
const cacheControl = headers['cache-control'] || '';
|
|
377
|
+
const expires = headers['expires'] || '';
|
|
378
|
+
const lastModified = headers['last-modified'] || '';
|
|
379
|
+
const etag = headers['etag'] || '';
|
|
380
|
+
const age = headers['age'] || '';
|
|
381
|
+
return {
|
|
382
|
+
cacheControl,
|
|
383
|
+
expires,
|
|
384
|
+
lastModified,
|
|
385
|
+
etag,
|
|
386
|
+
age,
|
|
387
|
+
hasCachingHeaders: !!(cacheControl || expires || lastModified || etag)
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Browser Caching Scorer
|
|
392
|
+
*/
|
|
393
|
+
function calculateBrowserCachingScore(data) {
|
|
394
|
+
let score = 0;
|
|
395
|
+
let displayValue = 'No caching headers detected';
|
|
396
|
+
const issues = [];
|
|
397
|
+
const goodHeaders = [];
|
|
398
|
+
// Check Cache-Control header
|
|
399
|
+
if (data.cacheControl) {
|
|
400
|
+
goodHeaders.push('Cache-Control');
|
|
401
|
+
// Check for good caching directives
|
|
402
|
+
if (data.cacheControl.includes('max-age') && !data.cacheControl.includes('no-cache') && !data.cacheControl.includes('no-store')) {
|
|
403
|
+
score = 1;
|
|
404
|
+
displayValue = 'Good caching headers found';
|
|
405
|
+
}
|
|
406
|
+
else if (data.cacheControl.includes('no-cache') || data.cacheControl.includes('no-store')) {
|
|
407
|
+
issues.push('Cache-Control prevents caching');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Check Expires header
|
|
411
|
+
if (data.expires) {
|
|
412
|
+
goodHeaders.push('Expires');
|
|
413
|
+
const expiresDate = new Date(data.expires);
|
|
414
|
+
const now = new Date();
|
|
415
|
+
if (expiresDate > now) {
|
|
416
|
+
if (score === 0)
|
|
417
|
+
score = 0.5; // Partial credit for expires header
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Check other headers
|
|
421
|
+
if (data.lastModified)
|
|
422
|
+
goodHeaders.push('Last-Modified');
|
|
423
|
+
if (data.etag)
|
|
424
|
+
goodHeaders.push('ETag');
|
|
425
|
+
if (data.age)
|
|
426
|
+
goodHeaders.push('Age');
|
|
427
|
+
if (goodHeaders.length > 0 && score === 0) {
|
|
428
|
+
score = 0.5; // Partial credit for having some caching headers
|
|
429
|
+
displayValue = `Basic caching headers found: ${goodHeaders.join(', ')}`;
|
|
430
|
+
}
|
|
431
|
+
if (issues.length > 0) {
|
|
432
|
+
displayValue += ` (${issues.join(', ')})`;
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
score,
|
|
436
|
+
displayValue,
|
|
437
|
+
details: {
|
|
438
|
+
headers: {
|
|
439
|
+
'cache-control': data.cacheControl,
|
|
440
|
+
'expires': data.expires,
|
|
441
|
+
'last-modified': data.lastModified,
|
|
442
|
+
'etag': data.etag,
|
|
443
|
+
'age': data.age
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Gzip Compression Data Collector
|
|
450
|
+
*/
|
|
451
|
+
function collectGzipCompressionData(response) {
|
|
452
|
+
// Get response headers from Puppeteer response object
|
|
453
|
+
const headers = {};
|
|
454
|
+
try {
|
|
455
|
+
const rawHeaders = response.headers();
|
|
456
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
457
|
+
headers[key.toLowerCase()] = value;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
console.warn('Failed to get response headers:', error);
|
|
462
|
+
}
|
|
463
|
+
const contentEncoding = headers['content-encoding'] || '';
|
|
464
|
+
const contentLength = headers['content-length'] || '';
|
|
465
|
+
const transferEncoding = headers['transfer-encoding'] || '';
|
|
466
|
+
return {
|
|
467
|
+
contentEncoding,
|
|
468
|
+
contentLength,
|
|
469
|
+
transferEncoding,
|
|
470
|
+
isCompressed: contentEncoding.includes('gzip') || contentEncoding.includes('deflate') || transferEncoding.includes('chunked')
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Gzip Compression Scorer
|
|
475
|
+
*/
|
|
476
|
+
function calculateGzipCompressionScore(data) {
|
|
477
|
+
let score = 0;
|
|
478
|
+
let displayValue = 'No compression detected';
|
|
479
|
+
if (data.isCompressed) {
|
|
480
|
+
score = 1;
|
|
481
|
+
displayValue = `Compression enabled: ${data.contentEncoding || data.transferEncoding}`;
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
displayValue = 'Response not compressed - consider enabling gzip compression';
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
score,
|
|
488
|
+
displayValue,
|
|
489
|
+
details: {
|
|
490
|
+
headers: {
|
|
491
|
+
'content-encoding': data.contentEncoding,
|
|
492
|
+
'content-length': data.contentLength,
|
|
493
|
+
'transfer-encoding': data.transferEncoding
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
232
498
|
/**
|
|
233
499
|
* Crawl the site to discover internal pages
|
|
234
500
|
*/
|
|
@@ -387,12 +653,19 @@ async function analyzeSinglePage(url) {
|
|
|
387
653
|
const collector = dataCollectors[metric.dataCollector];
|
|
388
654
|
const scorer = scorers[metric.scorer];
|
|
389
655
|
if (collector && scorer) {
|
|
390
|
-
|
|
656
|
+
// Pass response object for collectors that need headers (like browser caching and gzip compression)
|
|
657
|
+
const rawData = (metric.dataCollector === 'collectBrowserCachingData' || metric.dataCollector === 'collectGzipCompressionData')
|
|
658
|
+
? collector(html, pageData.title, response)
|
|
659
|
+
: collector(html, pageData.title);
|
|
391
660
|
const result = scorer(rawData);
|
|
392
661
|
score = result.score;
|
|
393
662
|
displayValue = result.displayValue;
|
|
394
663
|
details = result.details;
|
|
395
664
|
}
|
|
665
|
+
else {
|
|
666
|
+
score = 0;
|
|
667
|
+
displayValue = `Data collector or scorer not found: ${metric.dataCollector}/${metric.scorer}`;
|
|
668
|
+
}
|
|
396
669
|
}
|
|
397
670
|
else if (metric.pattern) {
|
|
398
671
|
// Use pattern-based analysis
|
|
@@ -401,6 +674,11 @@ async function analyzeSinglePage(url) {
|
|
|
401
674
|
displayValue = result.displayValue;
|
|
402
675
|
details = result.details;
|
|
403
676
|
}
|
|
677
|
+
else {
|
|
678
|
+
// Neither data collector/scorer nor pattern available
|
|
679
|
+
score = 0;
|
|
680
|
+
displayValue = 'Configuration incomplete - no pattern or data collector defined';
|
|
681
|
+
}
|
|
404
682
|
// Override H1 and H2 results with direct DOM counts for accuracy and speed
|
|
405
683
|
if (metric.id === 'h1-tags') {
|
|
406
684
|
score = pageData.h1Count === (metric.expectedCount || 1) ? 1 : 0;
|
|
@@ -520,6 +798,22 @@ async function performSiteWideAudits(baseUrl) {
|
|
|
520
798
|
displayValue = 'Manifest.webmanifest not accessible';
|
|
521
799
|
}
|
|
522
800
|
break;
|
|
801
|
+
case 'gzip-compression':
|
|
802
|
+
score = 0; // Placeholder - would need HTTP header analysis
|
|
803
|
+
displayValue = 'Requires server header analysis';
|
|
804
|
+
break;
|
|
805
|
+
case 'browser-caching':
|
|
806
|
+
score = 0; // Placeholder - would need HTTP header analysis
|
|
807
|
+
displayValue = 'Requires server header analysis';
|
|
808
|
+
break;
|
|
809
|
+
case 'duplicate-content-detection':
|
|
810
|
+
score = 0; // Placeholder - would need multi-page content analysis
|
|
811
|
+
displayValue = 'Requires comprehensive site crawl';
|
|
812
|
+
break;
|
|
813
|
+
case 'safe-browsing-status':
|
|
814
|
+
score = 0; // Placeholder - would need external API
|
|
815
|
+
displayValue = 'Requires Google Safe Browsing API';
|
|
816
|
+
break;
|
|
523
817
|
default:
|
|
524
818
|
score = 0;
|
|
525
819
|
displayValue = 'Not implemented';
|
package/dist/index.js
CHANGED
|
@@ -88,6 +88,7 @@ export * from './components/admin/site-health/site-health-github';
|
|
|
88
88
|
export * from './components/admin/site-health/site-health-google-analytics';
|
|
89
89
|
export * from './components/admin/site-health/site-health-google-search-console';
|
|
90
90
|
export * from './components/admin/site-health/site-health-on-site-seo';
|
|
91
|
+
export * from './components/admin/site-health/site-health-cloudwatch';
|
|
91
92
|
export * from './components/admin/site-health/seo-constants';
|
|
92
93
|
export * from './components/admin/site-health/site-health-overview';
|
|
93
94
|
export * from './components/admin/site-health/site-health-performance';
|
package/dist/index.server.js
CHANGED
|
@@ -14,6 +14,7 @@ export * from './components/admin/site-health/site-health-google-analytics.integ
|
|
|
14
14
|
export * from './components/admin/site-health/site-health-google-search-console.integration';
|
|
15
15
|
export * from './components/admin/site-health/site-health-indicators';
|
|
16
16
|
export * from './components/admin/site-health/site-health-on-site-seo.integration';
|
|
17
|
+
export * from './components/admin/site-health/site-health-cloudwatch.integration';
|
|
17
18
|
export * from './components/admin/site-health/seo-constants';
|
|
18
19
|
export * from './components/admin/site-health/site-health-security.integration';
|
|
19
20
|
export * from './components/admin/site-health/site-health-performance';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface SiteHealthCloudwatchProps {
|
|
2
|
+
siteName: string;
|
|
3
|
+
startDate?: string;
|
|
4
|
+
endDate?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function SiteHealthCloudwatch({ siteName, startDate, endDate }: SiteHealthCloudwatchProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=site-health-cloudwatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-health-cloudwatch.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-cloudwatch.tsx"],"names":[],"mappings":"AAcA,UAAU,yBAAyB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,yBAAyB,2CAiH/F"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudWatch Health Check Integration Services
|
|
3
|
+
* Server-side utilities for Route53 health check data retrieval via CloudWatch
|
|
4
|
+
*/
|
|
5
|
+
export interface CloudwatchHealthCheckConfig {
|
|
6
|
+
healthCheckId: string;
|
|
7
|
+
region?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface HealthCheckDataPoint {
|
|
10
|
+
date: string;
|
|
11
|
+
successCount: number;
|
|
12
|
+
failureCount: number;
|
|
13
|
+
totalChecks: number;
|
|
14
|
+
successRate: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CloudwatchHealthCheckResponse {
|
|
17
|
+
success: boolean;
|
|
18
|
+
data?: HealthCheckDataPoint[];
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get health check data for a site using CloudWatch metrics
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCloudwatchHealthCheckData(config: CloudwatchHealthCheckConfig, siteName: string, startDate?: string, endDate?: string): Promise<CloudwatchHealthCheckResponse>;
|
|
25
|
+
//# sourceMappingURL=site-health-cloudwatch.integration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-health-cloudwatch.integration.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-cloudwatch.integration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,6BAA6B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAKD;;GAEG;AACH,wBAAsB,4BAA4B,CACjD,MAAM,EAAE,2BAA2B,EACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,6BAA6B,CAAC,CAiJxC"}
|
package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-health-on-site-seo.integration.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-on-site-seo.integration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"site-health-on-site-seo.integration.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-on-site-seo.integration.ts"],"names":[],"mappings":"AA6jBA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,QAAQ,GAAG,eAAe,CAAC;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACxC,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqaD;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAwFtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pixelated.linkedin1.d.ts","sourceRoot":"","sources":["../../../../src/components/cms/pixelated.linkedin1.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pixelated.linkedin1.d.ts","sourceRoot":"","sources":["../../../../src/components/cms/pixelated.linkedin1.js"],"names":[],"mappings":"AAGA,oEAiGC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -87,6 +87,7 @@ export * from "./components/admin/site-health/site-health-github";
|
|
|
87
87
|
export * from "./components/admin/site-health/site-health-google-analytics";
|
|
88
88
|
export * from "./components/admin/site-health/site-health-google-search-console";
|
|
89
89
|
export * from "./components/admin/site-health/site-health-on-site-seo";
|
|
90
|
+
export * from "./components/admin/site-health/site-health-cloudwatch";
|
|
90
91
|
export * from "./components/admin/site-health/seo-constants";
|
|
91
92
|
export * from "./components/admin/site-health/site-health-overview";
|
|
92
93
|
export * from "./components/admin/site-health/site-health-performance";
|
|
@@ -10,6 +10,7 @@ export * from "./components/admin/site-health/site-health-google-analytics.integ
|
|
|
10
10
|
export * from "./components/admin/site-health/site-health-google-search-console.integration";
|
|
11
11
|
export * from "./components/admin/site-health/site-health-indicators";
|
|
12
12
|
export * from "./components/admin/site-health/site-health-on-site-seo.integration";
|
|
13
|
+
export * from "./components/admin/site-health/site-health-cloudwatch.integration";
|
|
13
14
|
export * from "./components/admin/site-health/seo-constants";
|
|
14
15
|
export * from "./components/admin/site-health/site-health-security.integration";
|
|
15
16
|
export * from "./components/admin/site-health/site-health-performance";
|
|
@@ -62,4 +62,8 @@ export declare const UptimeHealthCard: {
|
|
|
62
62
|
(): import("react/jsx-runtime").JSX.Element;
|
|
63
63
|
storyName: string;
|
|
64
64
|
};
|
|
65
|
+
export declare const CloudwatchHealthCard: {
|
|
66
|
+
(): import("react/jsx-runtime").JSX.Element;
|
|
67
|
+
storyName: string;
|
|
68
|
+
};
|
|
65
69
|
//# sourceMappingURL=site-health.stories.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-health.stories.d.ts","sourceRoot":"","sources":["../../../../src/stories/admin/site-health.stories.tsx"],"names":[],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"site-health.stories.d.ts","sourceRoot":"","sources":["../../../../src/stories/admin/site-health.stories.tsx"],"names":[],"mappings":";;;;;;;;;;;AAgBA,wBAUE;AAwEF,eAAO,MAAM,iBAAiB;;;CA6H7B,CAAC;AAIF,eAAO,MAAM,kBAAkB;;;CAkH9B,CAAC;AAIF,eAAO,MAAM,oBAAoB;;;CAkChC,CAAC;AAIF,eAAO,MAAM,qBAAqB;;;CAgCjC,CAAC;AAIF,eAAO,MAAM,kBAAkB;;;CA0B9B,CAAC;AAIF,eAAO,MAAM,aAAa;;;CA0BzB,CAAC;AAIF,eAAO,MAAM,uBAAuB;;;CA0BnC,CAAC;AAIF,eAAO,MAAM,yBAAyB;;;CAyBrC,CAAC;AAIF,eAAO,MAAM,6BAA6B;;;CAyBzC,CAAC;AAIF,eAAO,MAAM,mBAAmB;;;CA0B/B,CAAC;AAIF,eAAO,MAAM,mCAAmC;;;CA0B/C,CAAC;AAIF,eAAO,MAAM,gBAAgB;;;CAyB5B,CAAC;AAIF,eAAO,MAAM,gBAAgB;;;CA0B5B,CAAC;AAIF,eAAO,MAAM,oBAAoB;;;CA+BhC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-health-cloudwatch.test.d.ts","sourceRoot":"","sources":["../../../src/tests/site-health-cloudwatch.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-health-on-site-seo.integration.test.d.ts","sourceRoot":"","sources":["../../../src/tests/site-health-on-site-seo.integration.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixelated-tech/components",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pixelated Technologies",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"copy2pixelatedadmin": "rm -rf ../pixelated-admin/node_modules/@pixelated-tech/components/dist && cp -r dist ../pixelated-admin/node_modules/@pixelated-tech/components/",
|
|
67
67
|
"copy2all": "npm run copy2brianwhaley && npm run copy2informationfocus && npm run copy2oaktreelandscaping && npm run copy2palmettoepoxy && npm run copy2pixelated && npm run copy2pixelvivid && npm run copy2pixelatedtest && npm run copy2template && npm run copy2pixelatedadmin",
|
|
68
68
|
"storybook": "rm -rf node_modules/.cache && storybook dev -p 6006",
|
|
69
|
-
"buildStorybook": "rm -rf node_modules/.cache && storybook build",
|
|
69
|
+
"buildStorybook": "rm -rf node_modules/.cache && NODE_OPTIONS=\"--max-old-space-size=4096\" storybook build",
|
|
70
70
|
"test": "vitest",
|
|
71
71
|
"test:ui": "vitest --ui",
|
|
72
72
|
"test:coverage": "vitest run --coverage",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"build-webpack-rsync": "rsync -a --include='*' --include='*/' src/css dist/css"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
+
"@aws-sdk/client-cloudwatch": "^3.958.0",
|
|
82
83
|
"@aws-sdk/client-route-53": "^3.958.0",
|
|
83
84
|
"date-fns": "^4.1.0",
|
|
84
85
|
"globals": "^17.0.0",
|