lytx 0.3.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/.env.example +37 -0
- package/README.md +486 -0
- package/alchemy.run.ts +155 -0
- package/cli/bootstrap-admin.ts +284 -0
- package/cli/deploy-staging.ts +692 -0
- package/cli/import-events.ts +628 -0
- package/cli/import-sites.ts +518 -0
- package/cli/index.ts +609 -0
- package/cli/init-db.ts +269 -0
- package/cli/migrate-to-durable-objects.ts +564 -0
- package/cli/migration-worker.ts +300 -0
- package/cli/performance-test.ts +588 -0
- package/cli/pg/client.ts +4 -0
- package/cli/pg/new-site.ts +153 -0
- package/cli/rollback-durable-objects.ts +622 -0
- package/cli/seed-data.ts +459 -0
- package/cli/setup.js +18 -0
- package/cli/setup.ts +463 -0
- package/cli/validate-migration.ts +200 -0
- package/cli/wrangler-migration.jsonc +28 -0
- package/db/adapter.ts +166 -0
- package/db/analytics_engine/client.ts +0 -0
- package/db/analytics_engine/sites.ts +0 -0
- package/db/client.ts +16 -0
- package/db/d1/client.ts +8 -0
- package/db/d1/drizzle.config.ts +35 -0
- package/db/d1/migrations/0000_true_maelstrom.sql +165 -0
- package/db/d1/migrations/0001_wonderful_bloodaxe.sql +12 -0
- package/db/d1/migrations/0002_late_frightful_four.sql +1 -0
- package/db/d1/migrations/0003_cuddly_obadiah_stane.sql +16 -0
- package/db/d1/migrations/0004_mute_stardust.sql +1 -0
- package/db/d1/migrations/0005_awesome_silvermane.sql +3 -0
- package/db/d1/migrations/0006_volatile_shriek.sql +2 -0
- package/db/d1/migrations/0007_superb_lila_cheney.sql +1 -0
- package/db/d1/migrations/0008_bitter_longshot.sql +17 -0
- package/db/d1/migrations/0009_wonderful_madame_masque.sql +28 -0
- package/db/d1/migrations/meta/0000_snapshot.json +1112 -0
- package/db/d1/migrations/meta/0001_snapshot.json +1187 -0
- package/db/d1/migrations/meta/0002_snapshot.json +1194 -0
- package/db/d1/migrations/meta/0003_snapshot.json +1296 -0
- package/db/d1/migrations/meta/0004_snapshot.json +1303 -0
- package/db/d1/migrations/meta/0005_snapshot.json +1325 -0
- package/db/d1/migrations/meta/0006_snapshot.json +1339 -0
- package/db/d1/migrations/meta/0007_snapshot.json +1347 -0
- package/db/d1/migrations/meta/0008_snapshot.json +1464 -0
- package/db/d1/migrations/meta/0009_snapshot.json +1648 -0
- package/db/d1/migrations/meta/_journal.json +76 -0
- package/db/d1/schema.ts +407 -0
- package/db/d1/sites.ts +374 -0
- package/db/d1/teamAiUsage.ts +101 -0
- package/db/d1/teams.ts +127 -0
- package/db/durable/drizzle.config.ts +8 -0
- package/db/durable/durableObjectClient.ts +480 -0
- package/db/durable/events.ts +100 -0
- package/db/durable/migrations/0000_fair_bucky.sql +38 -0
- package/db/durable/migrations/meta/0000_snapshot.json +278 -0
- package/db/durable/migrations/meta/_journal.json +13 -0
- package/db/durable/migrations/migrations.js +10 -0
- package/db/durable/schema.ts +5 -0
- package/db/durable/siteDurableObject.ts +1352 -0
- package/db/durable/types.ts +53 -0
- package/db/postgres/client.ts +13 -0
- package/db/postgres/drizzle.config.ts +12 -0
- package/db/postgres/migrations/0000_brainy_sprite.sql +116 -0
- package/db/postgres/migrations/meta/0000_snapshot.json +681 -0
- package/db/postgres/migrations/meta/_journal.json +13 -0
- package/db/postgres/schema.ts +145 -0
- package/db/postgres/sites.ts +118 -0
- package/db/tranformReports.ts +595 -0
- package/db/types.ts +55 -0
- package/endpoints/api_worker.tsx +1854 -0
- package/endpoints/site_do_worker.ts +11 -0
- package/index.d.ts +63 -0
- package/index.ts +83 -0
- package/lib/auth.ts +279 -0
- package/lib/geojson/world_countries.json +45307 -0
- package/lib/random_name.ts +41 -0
- package/lib/sendMail.ts +252 -0
- package/package.json +142 -0
- package/public/favicon.ico +0 -0
- package/public/images/android-chrome-192x192.png +0 -0
- package/public/images/android-chrome-512x512.png +0 -0
- package/public/images/apple-touch-icon.png +0 -0
- package/public/images/favicon-16x16.png +0 -0
- package/public/images/favicon-32x32.png +0 -0
- package/public/images/lytx_dark_dashboard.png +0 -0
- package/public/images/lytx_light_dashboard.png +0 -0
- package/public/images/safari-pinned-tab.svg +4 -0
- package/public/logo.png +0 -0
- package/public/site.webmanifest +26 -0
- package/public/sw.js +107 -0
- package/src/Document.tsx +86 -0
- package/src/api/ai_api.ts +1156 -0
- package/src/api/authMiddleware.ts +45 -0
- package/src/api/auth_api.ts +465 -0
- package/src/api/event_labels_api.ts +193 -0
- package/src/api/events_api.ts +210 -0
- package/src/api/queueWorker.ts +303 -0
- package/src/api/reports_api.ts +278 -0
- package/src/api/seed_api.ts +288 -0
- package/src/api/sites_api.ts +904 -0
- package/src/api/tag_api.ts +458 -0
- package/src/api/tag_api_v2.ts +289 -0
- package/src/api/team_api.ts +456 -0
- package/src/app/Dashboard.tsx +1339 -0
- package/src/app/Events.tsx +974 -0
- package/src/app/Explore.tsx +312 -0
- package/src/app/Layout.tsx +58 -0
- package/src/app/Settings.tsx +1302 -0
- package/src/app/components/DashboardCard.tsx +118 -0
- package/src/app/components/EditableCell.tsx +123 -0
- package/src/app/components/EventForm.tsx +93 -0
- package/src/app/components/MarketingFooter.tsx +49 -0
- package/src/app/components/MarketingNav.tsx +150 -0
- package/src/app/components/Nav.tsx +755 -0
- package/src/app/components/NewSiteSetup.tsx +298 -0
- package/src/app/components/SQLEditor.tsx +740 -0
- package/src/app/components/SiteSelector.tsx +126 -0
- package/src/app/components/SiteTag.tsx +42 -0
- package/src/app/components/SiteTagInstallCard.tsx +241 -0
- package/src/app/components/WorldMapCard.tsx +337 -0
- package/src/app/components/charts/ChartComponents.tsx +1481 -0
- package/src/app/components/charts/EventFunnel.tsx +45 -0
- package/src/app/components/charts/EventSummary.tsx +194 -0
- package/src/app/components/charts/SankeyFlows.tsx +72 -0
- package/src/app/components/marketing/CheckIcon.tsx +16 -0
- package/src/app/components/marketing/MarketingLayout.tsx +23 -0
- package/src/app/components/marketing/SectionHeading.tsx +35 -0
- package/src/app/components/reports/AskAiWorkspace.tsx +371 -0
- package/src/app/components/reports/CreateReportStarter.tsx +74 -0
- package/src/app/components/reports/DashboardRouteFiltersContext.tsx +14 -0
- package/src/app/components/reports/DashboardToolbar.tsx +154 -0
- package/src/app/components/reports/DashboardWorkspaceLayout.tsx +63 -0
- package/src/app/components/reports/DashboardWorkspaceShell.tsx +118 -0
- package/src/app/components/reports/ReportBuilderWorkspace.tsx +76 -0
- package/src/app/components/reports/custom/CustomReportBuilderPage.tsx +1667 -0
- package/src/app/components/reports/custom/ReportWidgetChart.tsx +297 -0
- package/src/app/components/reports/custom/buildWidgetSql.ts +151 -0
- package/src/app/components/reports/custom/chartPalettes.ts +18 -0
- package/src/app/components/reports/custom/types.ts +50 -0
- package/src/app/components/reports/reportBuilderMenuItems.ts +17 -0
- package/src/app/components/reports/useDashboardToolbarControls.tsx +235 -0
- package/src/app/components/ui/AlertBanner.tsx +101 -0
- package/src/app/components/ui/Button.tsx +55 -0
- package/src/app/components/ui/Card.tsx +80 -0
- package/src/app/components/ui/Input.tsx +72 -0
- package/src/app/components/ui/Link.tsx +23 -0
- package/src/app/components/ui/ReportBuilderMenu.tsx +246 -0
- package/src/app/components/ui/ThemeToggle.tsx +54 -0
- package/src/app/constants.ts +6 -0
- package/src/app/headers.ts +33 -0
- package/src/app/providers/AuthProvider.tsx +189 -0
- package/src/app/providers/ClientProviders.tsx +18 -0
- package/src/app/providers/QueryProvider.tsx +23 -0
- package/src/app/providers/ThemeProvider.tsx +88 -0
- package/src/app/utils/chartThemes.ts +146 -0
- package/src/app/utils/keybinds.ts +96 -0
- package/src/app/utils/media.tsx +24 -0
- package/src/client.tsx +114 -0
- package/src/config/createLytxAppConfig.ts +252 -0
- package/src/config/resourceNames.ts +88 -0
- package/src/db/index.ts +67 -0
- package/src/index.css +285 -0
- package/src/lib/featureFlags.ts +69 -0
- package/src/pages/GetStarted.tsx +290 -0
- package/src/pages/Home.tsx +268 -0
- package/src/pages/Login.tsx +283 -0
- package/src/pages/PrivacyPolicy.tsx +120 -0
- package/src/pages/Signup.tsx +267 -0
- package/src/pages/TermsOfService.tsx +126 -0
- package/src/pages/VerifyEmail.tsx +56 -0
- package/src/session/durableObject.ts +7 -0
- package/src/session/siteSchema.ts +86 -0
- package/src/session/types.ts +36 -0
- package/src/templates/README.md +80 -0
- package/src/templates/cleanFunctions.js +44 -0
- package/src/templates/embedFunctions.js +52 -0
- package/src/templates/lytx-shared.ts +662 -0
- package/src/templates/lytxpixel-core.ts +144 -0
- package/src/templates/lytxpixel.ts +267 -0
- package/src/templates/lytxpixelBrowser.js +634 -0
- package/src/templates/lytxpixelBrowser.mjs +634 -0
- package/src/templates/parseData.js +12 -0
- package/src/templates/script.ts +31 -0
- package/src/templates/template.tsx +50 -0
- package/src/templates/test.js +3 -0
- package/src/templates/trackWebEvents.ts +177 -0
- package/src/templates/vendors/clickcease.ts +8 -0
- package/src/templates/vendors/google.ts +174 -0
- package/src/templates/vendors/linkedin.ts +23 -0
- package/src/templates/vendors/meta.ts +56 -0
- package/src/templates/vendors/quantcast.ts +22 -0
- package/src/templates/vendors/simplfi.ts +7 -0
- package/src/types/app-context.ts +16 -0
- package/src/utilities/dashboardParams.ts +188 -0
- package/src/utilities/dashboardQueries.ts +537 -0
- package/src/utilities/dashboardTransforms.ts +167 -0
- package/src/utilities/dataValidation.ts +414 -0
- package/src/utilities/detector.ts +73 -0
- package/src/utilities/encrypt.ts +103 -0
- package/src/utilities/index.ts +13 -0
- package/src/utilities/parser.ts +117 -0
- package/src/utilities/performanceMonitoring.ts +570 -0
- package/src/utilities/route_interuptors.ts +24 -0
- package/src/worker.tsx +675 -0
- package/tsconfig.json +78 -0
- package/types/env.d.ts +16 -0
- package/types/rw.d.ts +7 -0
- package/types/shims.d.ts +53 -0
- package/types/vite.d.ts +19 -0
- package/vite/vite-plugin-pixel-bundle.ts +126 -0
- package/vite.config.ts +53 -0
- package/worker-configuration.d.ts +8401 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Monitoring and Alerting System
|
|
3
|
+
*
|
|
4
|
+
* This module provides real-time performance monitoring for the durable object system,
|
|
5
|
+
* tracking key metrics and triggering alerts when thresholds are exceeded.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Performance metrics interface
|
|
10
|
+
*/
|
|
11
|
+
export interface PerformanceMetrics {
|
|
12
|
+
timestamp: Date;
|
|
13
|
+
siteId?: number;
|
|
14
|
+
|
|
15
|
+
// Response time metrics (milliseconds)
|
|
16
|
+
responseTime: {
|
|
17
|
+
dashboard: number;
|
|
18
|
+
eventIngestion: number;
|
|
19
|
+
api: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Throughput metrics (requests/events per second)
|
|
23
|
+
throughput: {
|
|
24
|
+
dashboardRequests: number;
|
|
25
|
+
eventIngestion: number;
|
|
26
|
+
apiRequests: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Error metrics (percentage)
|
|
30
|
+
errorRates: {
|
|
31
|
+
dashboard: number;
|
|
32
|
+
eventIngestion: number;
|
|
33
|
+
api: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Resource utilization
|
|
37
|
+
resources: {
|
|
38
|
+
memoryUsage: number; // MB
|
|
39
|
+
cpuUsage: number; // percentage
|
|
40
|
+
durableObjectCount: number;
|
|
41
|
+
queueDepth: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Business metrics
|
|
45
|
+
business: {
|
|
46
|
+
activeUsers: number;
|
|
47
|
+
eventsPerMinute: number;
|
|
48
|
+
sitesActive: number;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Performance thresholds for alerting
|
|
54
|
+
*/
|
|
55
|
+
export interface PerformanceThresholds {
|
|
56
|
+
responseTime: {
|
|
57
|
+
dashboard: { warning: number; critical: number };
|
|
58
|
+
eventIngestion: { warning: number; critical: number };
|
|
59
|
+
api: { warning: number; critical: number };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
throughput: {
|
|
63
|
+
dashboardRequests: { warning: number; critical: number };
|
|
64
|
+
eventIngestion: { warning: number; critical: number };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
errorRates: {
|
|
68
|
+
warning: number; // percentage
|
|
69
|
+
critical: number; // percentage
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
resources: {
|
|
73
|
+
memoryUsage: { warning: number; critical: number }; // MB
|
|
74
|
+
cpuUsage: { warning: number; critical: number }; // percentage
|
|
75
|
+
queueDepth: { warning: number; critical: number };
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Default performance thresholds
|
|
81
|
+
*/
|
|
82
|
+
export const DEFAULT_THRESHOLDS: PerformanceThresholds = {
|
|
83
|
+
responseTime: {
|
|
84
|
+
dashboard: { warning: 100, critical: 500 }, // ms
|
|
85
|
+
eventIngestion: { warning: 50, critical: 200 }, // ms
|
|
86
|
+
api: { warning: 200, critical: 1000 } // ms
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
throughput: {
|
|
90
|
+
dashboardRequests: { warning: 100, critical: 50 }, // req/sec
|
|
91
|
+
eventIngestion: { warning: 500, critical: 100 } // events/sec
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
errorRates: {
|
|
95
|
+
warning: 1, // 1%
|
|
96
|
+
critical: 5 // 5%
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
resources: {
|
|
100
|
+
memoryUsage: { warning: 512, critical: 1024 }, // MB
|
|
101
|
+
cpuUsage: { warning: 70, critical: 90 }, // percentage
|
|
102
|
+
queueDepth: { warning: 1000, critical: 5000 }
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Alert severity levels
|
|
108
|
+
*/
|
|
109
|
+
export enum AlertSeverity {
|
|
110
|
+
INFO = 'info',
|
|
111
|
+
WARNING = 'warning',
|
|
112
|
+
CRITICAL = 'critical'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Performance alert interface
|
|
117
|
+
*/
|
|
118
|
+
export interface PerformanceAlert {
|
|
119
|
+
id: string;
|
|
120
|
+
timestamp: Date;
|
|
121
|
+
severity: AlertSeverity;
|
|
122
|
+
metric: string;
|
|
123
|
+
value: number;
|
|
124
|
+
threshold: number;
|
|
125
|
+
message: string;
|
|
126
|
+
siteId?: number;
|
|
127
|
+
resolved?: boolean;
|
|
128
|
+
resolvedAt?: Date;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Performance monitoring class
|
|
133
|
+
*/
|
|
134
|
+
export class PerformanceMonitor {
|
|
135
|
+
private metrics: PerformanceMetrics[] = [];
|
|
136
|
+
private alerts: PerformanceAlert[] = [];
|
|
137
|
+
private thresholds: PerformanceThresholds;
|
|
138
|
+
private alertCallbacks: ((alert: PerformanceAlert) => void)[] = [];
|
|
139
|
+
|
|
140
|
+
constructor(thresholds: PerformanceThresholds = DEFAULT_THRESHOLDS) {
|
|
141
|
+
this.thresholds = thresholds;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Record performance metrics
|
|
146
|
+
*/
|
|
147
|
+
recordMetrics(metrics: PerformanceMetrics): void {
|
|
148
|
+
this.metrics.push(metrics);
|
|
149
|
+
|
|
150
|
+
// Keep only last 1000 metrics to prevent memory issues
|
|
151
|
+
if (this.metrics.length > 1000) {
|
|
152
|
+
this.metrics = this.metrics.slice(-1000);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for threshold violations
|
|
156
|
+
this.checkThresholds(metrics);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check metrics against thresholds and generate alerts
|
|
161
|
+
*/
|
|
162
|
+
private checkThresholds(metrics: PerformanceMetrics): void {
|
|
163
|
+
const alerts: PerformanceAlert[] = [];
|
|
164
|
+
|
|
165
|
+
// Response time checks
|
|
166
|
+
if (metrics.responseTime.dashboard > this.thresholds.responseTime.dashboard.critical) {
|
|
167
|
+
alerts.push(this.createAlert(
|
|
168
|
+
AlertSeverity.CRITICAL,
|
|
169
|
+
'dashboard_response_time',
|
|
170
|
+
metrics.responseTime.dashboard,
|
|
171
|
+
this.thresholds.responseTime.dashboard.critical,
|
|
172
|
+
`Dashboard response time is critically high: ${metrics.responseTime.dashboard}ms`,
|
|
173
|
+
metrics.siteId
|
|
174
|
+
));
|
|
175
|
+
} else if (metrics.responseTime.dashboard > this.thresholds.responseTime.dashboard.warning) {
|
|
176
|
+
alerts.push(this.createAlert(
|
|
177
|
+
AlertSeverity.WARNING,
|
|
178
|
+
'dashboard_response_time',
|
|
179
|
+
metrics.responseTime.dashboard,
|
|
180
|
+
this.thresholds.responseTime.dashboard.warning,
|
|
181
|
+
`Dashboard response time is elevated: ${metrics.responseTime.dashboard}ms`,
|
|
182
|
+
metrics.siteId
|
|
183
|
+
));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Event ingestion response time
|
|
187
|
+
if (metrics.responseTime.eventIngestion > this.thresholds.responseTime.eventIngestion.critical) {
|
|
188
|
+
alerts.push(this.createAlert(
|
|
189
|
+
AlertSeverity.CRITICAL,
|
|
190
|
+
'event_ingestion_response_time',
|
|
191
|
+
metrics.responseTime.eventIngestion,
|
|
192
|
+
this.thresholds.responseTime.eventIngestion.critical,
|
|
193
|
+
`Event ingestion response time is critically high: ${metrics.responseTime.eventIngestion}ms`,
|
|
194
|
+
metrics.siteId
|
|
195
|
+
));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Throughput checks
|
|
199
|
+
if (metrics.throughput.eventIngestion < this.thresholds.throughput.eventIngestion.critical) {
|
|
200
|
+
alerts.push(this.createAlert(
|
|
201
|
+
AlertSeverity.CRITICAL,
|
|
202
|
+
'event_ingestion_throughput',
|
|
203
|
+
metrics.throughput.eventIngestion,
|
|
204
|
+
this.thresholds.throughput.eventIngestion.critical,
|
|
205
|
+
`Event ingestion throughput is critically low: ${metrics.throughput.eventIngestion} events/sec`,
|
|
206
|
+
metrics.siteId
|
|
207
|
+
));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Error rate checks
|
|
211
|
+
const avgErrorRate = (metrics.errorRates.dashboard + metrics.errorRates.eventIngestion + metrics.errorRates.api) / 3;
|
|
212
|
+
if (avgErrorRate > this.thresholds.errorRates.critical) {
|
|
213
|
+
alerts.push(this.createAlert(
|
|
214
|
+
AlertSeverity.CRITICAL,
|
|
215
|
+
'error_rate',
|
|
216
|
+
avgErrorRate,
|
|
217
|
+
this.thresholds.errorRates.critical,
|
|
218
|
+
`Average error rate is critically high: ${avgErrorRate.toFixed(2)}%`,
|
|
219
|
+
metrics.siteId
|
|
220
|
+
));
|
|
221
|
+
} else if (avgErrorRate > this.thresholds.errorRates.warning) {
|
|
222
|
+
alerts.push(this.createAlert(
|
|
223
|
+
AlertSeverity.WARNING,
|
|
224
|
+
'error_rate',
|
|
225
|
+
avgErrorRate,
|
|
226
|
+
this.thresholds.errorRates.warning,
|
|
227
|
+
`Average error rate is elevated: ${avgErrorRate.toFixed(2)}%`,
|
|
228
|
+
metrics.siteId
|
|
229
|
+
));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Resource utilization checks
|
|
233
|
+
if (metrics.resources.memoryUsage > this.thresholds.resources.memoryUsage.critical) {
|
|
234
|
+
alerts.push(this.createAlert(
|
|
235
|
+
AlertSeverity.CRITICAL,
|
|
236
|
+
'memory_usage',
|
|
237
|
+
metrics.resources.memoryUsage,
|
|
238
|
+
this.thresholds.resources.memoryUsage.critical,
|
|
239
|
+
`Memory usage is critically high: ${metrics.resources.memoryUsage}MB`,
|
|
240
|
+
metrics.siteId
|
|
241
|
+
));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (metrics.resources.queueDepth > this.thresholds.resources.queueDepth.critical) {
|
|
245
|
+
alerts.push(this.createAlert(
|
|
246
|
+
AlertSeverity.CRITICAL,
|
|
247
|
+
'queue_depth',
|
|
248
|
+
metrics.resources.queueDepth,
|
|
249
|
+
this.thresholds.resources.queueDepth.critical,
|
|
250
|
+
`Queue depth is critically high: ${metrics.resources.queueDepth} items`,
|
|
251
|
+
metrics.siteId
|
|
252
|
+
));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Process alerts
|
|
256
|
+
for (const alert of alerts) {
|
|
257
|
+
this.processAlert(alert);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Create a performance alert
|
|
263
|
+
*/
|
|
264
|
+
private createAlert(
|
|
265
|
+
severity: AlertSeverity,
|
|
266
|
+
metric: string,
|
|
267
|
+
value: number,
|
|
268
|
+
threshold: number,
|
|
269
|
+
message: string,
|
|
270
|
+
siteId?: number
|
|
271
|
+
): PerformanceAlert {
|
|
272
|
+
return {
|
|
273
|
+
id: `${metric}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
274
|
+
timestamp: new Date(),
|
|
275
|
+
severity,
|
|
276
|
+
metric,
|
|
277
|
+
value,
|
|
278
|
+
threshold,
|
|
279
|
+
message,
|
|
280
|
+
siteId,
|
|
281
|
+
resolved: false
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Process and store alert
|
|
287
|
+
*/
|
|
288
|
+
private processAlert(alert: PerformanceAlert): void {
|
|
289
|
+
// Check if similar alert already exists and is unresolved
|
|
290
|
+
const existingAlert = this.alerts.find(a =>
|
|
291
|
+
a.metric === alert.metric &&
|
|
292
|
+
a.siteId === alert.siteId &&
|
|
293
|
+
!a.resolved
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (existingAlert) {
|
|
297
|
+
// Update existing alert
|
|
298
|
+
existingAlert.value = alert.value;
|
|
299
|
+
existingAlert.timestamp = alert.timestamp;
|
|
300
|
+
existingAlert.message = alert.message;
|
|
301
|
+
} else {
|
|
302
|
+
// Add new alert
|
|
303
|
+
this.alerts.push(alert);
|
|
304
|
+
|
|
305
|
+
// Keep only last 500 alerts
|
|
306
|
+
if (this.alerts.length > 500) {
|
|
307
|
+
this.alerts = this.alerts.slice(-500);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Notify alert callbacks
|
|
312
|
+
this.alertCallbacks.forEach(callback => {
|
|
313
|
+
try {
|
|
314
|
+
callback(alert);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error('Error in alert callback:', error);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Register alert callback
|
|
323
|
+
*/
|
|
324
|
+
onAlert(callback: (alert: PerformanceAlert) => void): void {
|
|
325
|
+
this.alertCallbacks.push(callback);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Resolve an alert
|
|
330
|
+
*/
|
|
331
|
+
resolveAlert(alertId: string): boolean {
|
|
332
|
+
const alert = this.alerts.find(a => a.id === alertId);
|
|
333
|
+
if (alert && !alert.resolved) {
|
|
334
|
+
alert.resolved = true;
|
|
335
|
+
alert.resolvedAt = new Date();
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get current metrics summary
|
|
343
|
+
*/
|
|
344
|
+
getCurrentMetrics(): PerformanceMetrics | null {
|
|
345
|
+
return this.metrics.length > 0 ? this.metrics[this.metrics.length - 1] : null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get metrics history
|
|
350
|
+
*/
|
|
351
|
+
getMetricsHistory(minutes: number = 60): PerformanceMetrics[] {
|
|
352
|
+
const cutoff = new Date(Date.now() - minutes * 60 * 1000);
|
|
353
|
+
return this.metrics.filter(m => m.timestamp >= cutoff);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get active alerts
|
|
358
|
+
*/
|
|
359
|
+
getActiveAlerts(): PerformanceAlert[] {
|
|
360
|
+
return this.alerts.filter(a => !a.resolved);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get alert history
|
|
365
|
+
*/
|
|
366
|
+
getAlertHistory(hours: number = 24): PerformanceAlert[] {
|
|
367
|
+
const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
368
|
+
return this.alerts.filter(a => a.timestamp >= cutoff);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Generate performance report
|
|
373
|
+
*/
|
|
374
|
+
generateReport(hours: number = 24): string {
|
|
375
|
+
const metrics = this.getMetricsHistory(hours * 60);
|
|
376
|
+
const alerts = this.getAlertHistory(hours);
|
|
377
|
+
|
|
378
|
+
if (metrics.length === 0) {
|
|
379
|
+
return 'No performance data available for the specified time period.';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const lines: string[] = [];
|
|
383
|
+
|
|
384
|
+
lines.push(`📊 Performance Report (Last ${hours} hours)`);
|
|
385
|
+
lines.push('=' .repeat(50));
|
|
386
|
+
lines.push('');
|
|
387
|
+
|
|
388
|
+
// Calculate averages
|
|
389
|
+
const avgDashboardTime = metrics.reduce((sum, m) => sum + m.responseTime.dashboard, 0) / metrics.length;
|
|
390
|
+
const avgEventIngestionTime = metrics.reduce((sum, m) => sum + m.responseTime.eventIngestion, 0) / metrics.length;
|
|
391
|
+
const avgThroughput = metrics.reduce((sum, m) => sum + m.throughput.eventIngestion, 0) / metrics.length;
|
|
392
|
+
const avgErrorRate = metrics.reduce((sum, m) => sum + (m.errorRates.dashboard + m.errorRates.eventIngestion + m.errorRates.api) / 3, 0) / metrics.length;
|
|
393
|
+
|
|
394
|
+
lines.push('📈 Average Performance Metrics:');
|
|
395
|
+
lines.push(`Dashboard Response Time: ${avgDashboardTime.toFixed(2)}ms`);
|
|
396
|
+
lines.push(`Event Ingestion Time: ${avgEventIngestionTime.toFixed(2)}ms`);
|
|
397
|
+
lines.push(`Event Throughput: ${avgThroughput.toFixed(2)} events/sec`);
|
|
398
|
+
lines.push(`Error Rate: ${avgErrorRate.toFixed(2)}%`);
|
|
399
|
+
lines.push('');
|
|
400
|
+
|
|
401
|
+
// Alert summary
|
|
402
|
+
const criticalAlerts = alerts.filter(a => a.severity === AlertSeverity.CRITICAL);
|
|
403
|
+
const warningAlerts = alerts.filter(a => a.severity === AlertSeverity.WARNING);
|
|
404
|
+
const activeAlerts = alerts.filter(a => !a.resolved);
|
|
405
|
+
|
|
406
|
+
lines.push('🚨 Alert Summary:');
|
|
407
|
+
lines.push(`Critical Alerts: ${criticalAlerts.length}`);
|
|
408
|
+
lines.push(`Warning Alerts: ${warningAlerts.length}`);
|
|
409
|
+
lines.push(`Active Alerts: ${activeAlerts.length}`);
|
|
410
|
+
lines.push('');
|
|
411
|
+
|
|
412
|
+
// Recent alerts
|
|
413
|
+
if (activeAlerts.length > 0) {
|
|
414
|
+
lines.push('🔥 Active Alerts:');
|
|
415
|
+
activeAlerts.slice(0, 5).forEach(alert => {
|
|
416
|
+
const icon = alert.severity === AlertSeverity.CRITICAL ? '🔴' : '🟡';
|
|
417
|
+
lines.push(`${icon} ${alert.message}`);
|
|
418
|
+
});
|
|
419
|
+
lines.push('');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Performance trends
|
|
423
|
+
if (metrics.length >= 2) {
|
|
424
|
+
const recent = metrics.slice(-10);
|
|
425
|
+
const older = metrics.slice(0, 10);
|
|
426
|
+
|
|
427
|
+
const recentAvgDashboard = recent.reduce((sum, m) => sum + m.responseTime.dashboard, 0) / recent.length;
|
|
428
|
+
const olderAvgDashboard = older.reduce((sum, m) => sum + m.responseTime.dashboard, 0) / older.length;
|
|
429
|
+
|
|
430
|
+
const trend = recentAvgDashboard > olderAvgDashboard ? '📈 Increasing' : '📉 Decreasing';
|
|
431
|
+
const change = Math.abs(((recentAvgDashboard - olderAvgDashboard) / olderAvgDashboard) * 100);
|
|
432
|
+
|
|
433
|
+
lines.push('📊 Performance Trends:');
|
|
434
|
+
lines.push(`Dashboard Response Time: ${trend} (${change.toFixed(1)}% change)`);
|
|
435
|
+
lines.push('');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Recommendations
|
|
439
|
+
lines.push('💡 Recommendations:');
|
|
440
|
+
if (avgDashboardTime > this.thresholds.responseTime.dashboard.warning) {
|
|
441
|
+
lines.push('- Consider optimizing dashboard queries or adding caching');
|
|
442
|
+
}
|
|
443
|
+
if (avgThroughput < this.thresholds.throughput.eventIngestion.warning) {
|
|
444
|
+
lines.push('- Review event ingestion pipeline for bottlenecks');
|
|
445
|
+
}
|
|
446
|
+
if (avgErrorRate > this.thresholds.errorRates.warning) {
|
|
447
|
+
lines.push('- Investigate error patterns and implement fixes');
|
|
448
|
+
}
|
|
449
|
+
if (activeAlerts.length > 0) {
|
|
450
|
+
lines.push('- Address active alerts to improve system stability');
|
|
451
|
+
}
|
|
452
|
+
if (lines.length === 1) {
|
|
453
|
+
lines.push('- System performance is within acceptable thresholds');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return lines.join('\n');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Global performance monitor instance
|
|
462
|
+
*/
|
|
463
|
+
export const performanceMonitor = new PerformanceMonitor();
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Middleware for automatic performance tracking
|
|
467
|
+
*/
|
|
468
|
+
export function createPerformanceMiddleware(monitor: PerformanceMonitor = performanceMonitor) {
|
|
469
|
+
return {
|
|
470
|
+
/**
|
|
471
|
+
* Track dashboard request performance
|
|
472
|
+
*/
|
|
473
|
+
trackDashboardRequest: async <T>(operation: () => Promise<T>): Promise<T> => {
|
|
474
|
+
const startTime = performance.now();
|
|
475
|
+
let error = false;
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const result = await operation();
|
|
479
|
+
return result;
|
|
480
|
+
} catch (err) {
|
|
481
|
+
error = true;
|
|
482
|
+
throw err;
|
|
483
|
+
} finally {
|
|
484
|
+
const endTime = performance.now();
|
|
485
|
+
const responseTime = endTime - startTime;
|
|
486
|
+
|
|
487
|
+
// Record metrics (simplified - in real implementation, aggregate with other metrics)
|
|
488
|
+
monitor.recordMetrics({
|
|
489
|
+
timestamp: new Date(),
|
|
490
|
+
responseTime: {
|
|
491
|
+
dashboard: responseTime,
|
|
492
|
+
eventIngestion: 0,
|
|
493
|
+
api: 0
|
|
494
|
+
},
|
|
495
|
+
throughput: {
|
|
496
|
+
dashboardRequests: 1,
|
|
497
|
+
eventIngestion: 0,
|
|
498
|
+
apiRequests: 0
|
|
499
|
+
},
|
|
500
|
+
errorRates: {
|
|
501
|
+
dashboard: error ? 100 : 0,
|
|
502
|
+
eventIngestion: 0,
|
|
503
|
+
api: 0
|
|
504
|
+
},
|
|
505
|
+
resources: {
|
|
506
|
+
memoryUsage: 0,
|
|
507
|
+
cpuUsage: 0,
|
|
508
|
+
durableObjectCount: 0,
|
|
509
|
+
queueDepth: 0
|
|
510
|
+
},
|
|
511
|
+
business: {
|
|
512
|
+
activeUsers: 0,
|
|
513
|
+
eventsPerMinute: 0,
|
|
514
|
+
sitesActive: 0
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Track event ingestion performance
|
|
522
|
+
*/
|
|
523
|
+
trackEventIngestion: async <T>(eventCount: number, operation: () => Promise<T>): Promise<T> => {
|
|
524
|
+
const startTime = performance.now();
|
|
525
|
+
let error = false;
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const result = await operation();
|
|
529
|
+
return result;
|
|
530
|
+
} catch (err) {
|
|
531
|
+
error = true;
|
|
532
|
+
throw err;
|
|
533
|
+
} finally {
|
|
534
|
+
const endTime = performance.now();
|
|
535
|
+
const responseTime = endTime - startTime;
|
|
536
|
+
const throughput = (eventCount / responseTime) * 1000; // events per second
|
|
537
|
+
|
|
538
|
+
monitor.recordMetrics({
|
|
539
|
+
timestamp: new Date(),
|
|
540
|
+
responseTime: {
|
|
541
|
+
dashboard: 0,
|
|
542
|
+
eventIngestion: responseTime,
|
|
543
|
+
api: 0
|
|
544
|
+
},
|
|
545
|
+
throughput: {
|
|
546
|
+
dashboardRequests: 0,
|
|
547
|
+
eventIngestion: throughput,
|
|
548
|
+
apiRequests: 0
|
|
549
|
+
},
|
|
550
|
+
errorRates: {
|
|
551
|
+
dashboard: 0,
|
|
552
|
+
eventIngestion: error ? 100 : 0,
|
|
553
|
+
api: 0
|
|
554
|
+
},
|
|
555
|
+
resources: {
|
|
556
|
+
memoryUsage: 0,
|
|
557
|
+
cpuUsage: 0,
|
|
558
|
+
durableObjectCount: 0,
|
|
559
|
+
queueDepth: 0
|
|
560
|
+
},
|
|
561
|
+
business: {
|
|
562
|
+
activeUsers: 0,
|
|
563
|
+
eventsPerMinute: eventCount,
|
|
564
|
+
sitesActive: 0
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AppContext } from "@/types/app-context";
|
|
2
|
+
import type { RequestInfo } from "rwsdk/worker";
|
|
3
|
+
|
|
4
|
+
export function checkIfTeamSetupSites({ ctx }: RequestInfo<any, AppContext>) {
|
|
5
|
+
if (!ctx.initial_site_setup) {
|
|
6
|
+
return new Response("User needs to create a site", {
|
|
7
|
+
status: 303,
|
|
8
|
+
headers: { location: "/dashboard/new-site" },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
if (!ctx.sites || ctx.sites.length === 0) {
|
|
12
|
+
return new Response("No sites are assigned to this user", {
|
|
13
|
+
status: 403,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function onlyAllowGetPost({ request }: RequestInfo<any, AppContext>) {
|
|
18
|
+
if (!["GET", "POST"].includes(request.method))
|
|
19
|
+
return new Response("Not Found.", { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
export function onlyAllowPost({ request }: RequestInfo<any, AppContext>) {
|
|
22
|
+
if (!["POST"].includes(request.method))
|
|
23
|
+
return new Response("Not Found.", { status: 404 });
|
|
24
|
+
}
|