@littlebearapps/platform-admin-sdk 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +86 -2
- package/package.json +1 -1
- package/templates/full/dashboard/src/components/reports/DigestStats.tsx +151 -0
- package/templates/full/dashboard/src/components/reports/HealthTrendsReport.tsx +192 -0
- package/templates/full/dashboard/src/components/usage/AIModelBreakdown.tsx +364 -0
- package/templates/full/dashboard/src/components/usage/unified/Recommendations.tsx +149 -0
- package/templates/full/dashboard/src/lib/cloudflare/alerting.ts +486 -0
- package/templates/full/dashboard/src/lib/cloudflare/graphql.ts +4785 -0
- package/templates/full/dashboard/src/lib/cloudflare/project-registry.ts +451 -0
- package/templates/full/dashboard/src/lib/notifications/api.ts +197 -0
- package/templates/full/dashboard/src/lib/notifications/types.ts.hbs +97 -0
- package/templates/full/dashboard/src/lib/patterns/api.ts +120 -0
- package/templates/full/dashboard/src/lib/patterns/types.ts +127 -0
- package/templates/full/dashboard/src/lib/reports/types.ts +231 -0
- package/templates/full/dashboard/src/lib/search/api.ts +258 -0
- package/templates/full/dashboard/src/lib/search/types.ts.hbs +115 -0
- package/templates/full/dashboard/src/lib/settings/api.ts.hbs +201 -0
- package/templates/full/dashboard/src/lib/settings/types.ts.hbs +104 -0
- package/templates/full/dashboard/src/lib/usage/allowance-config.ts.hbs +547 -0
- package/templates/full/dashboard/src/lib/usage/providers.ts +331 -0
- package/templates/shared/dashboard/src/components/reports/ReportInfoButton.tsx +98 -0
- package/templates/shared/dashboard/src/components/usage/react/DashboardShell.tsx +263 -0
- package/templates/shared/dashboard/src/components/usage/react/StatusBadge.tsx +77 -0
- package/templates/shared/dashboard/src/components/usage/react/UsageChart.tsx +391 -0
- package/templates/shared/dashboard/src/components/usage/react/index.ts.hbs +30 -0
- package/templates/shared/dashboard/src/components/usage/react/types.ts +137 -0
- package/templates/shared/dashboard/src/components/usage/transformers.ts +478 -0
- package/templates/shared/dashboard/src/components/usage/unified/AlertBanner.tsx +172 -0
- package/templates/shared/dashboard/src/components/usage/unified/HeroCardsRow.tsx +757 -0
- package/templates/shared/dashboard/src/components/usage/unified/LiveHeader.tsx +169 -0
- package/templates/shared/dashboard/src/components/usage/unified/ProjectsTable.tsx +448 -0
- package/templates/shared/dashboard/src/components/usage/unified/ResourceBreakdown.tsx +236 -0
- package/templates/shared/dashboard/src/components/usage/unified/Sparkline.tsx +127 -0
- package/templates/shared/dashboard/src/components/usage/unified/UnifiedShell.tsx +893 -0
- package/templates/shared/dashboard/src/components/usage/unified/index.ts.hbs +50 -0
- package/templates/shared/dashboard/src/components/usage/unified/types.ts +416 -0
- package/templates/shared/dashboard/src/lib/cloudflare/analytics.ts +310 -0
- package/templates/shared/dashboard/src/lib/cloudflare/d1.ts +55 -0
- package/templates/shared/dashboard/src/lib/cloudflare/index.ts.hbs +120 -0
- package/templates/shared/dashboard/src/lib/infrastructure/types.ts +116 -0
- package/templates/shared/dashboard/src/lib/usage/fetchWithDedup.ts +101 -0
- package/templates/shared/dashboard/src/lib/usage/index.ts.hbs +12 -0
- package/templates/shared/tests/e2e/usage-api.test.ts +909 -0
- package/templates/shared/tests/helpers/mock-storage.ts +166 -0
- package/templates/shared/tests/integration/kv-cache.test.ts +252 -0
- package/templates/shared/tests/integration/platform-usage.test.ts +956 -0
- package/templates/shared/tests/unit/billing.test.ts +331 -0
- package/templates/shared/tests/unit/cloudflare/graphql.test.ts +217 -0
- package/templates/shared/tests/unit/components/usage-transformers.test.ts +473 -0
- package/templates/shared/tests/unit/control.test.ts +226 -0
- package/templates/shared/tests/unit/cost-calculator.test.ts +141 -0
- package/templates/shared/tests/unit/economics.test.ts +365 -0
- package/templates/shared/tests/unit/telemetry-sampling.test.ts +401 -0
- package/templates/standard/dashboard/src/components/reports/CircuitBreakerReport.tsx +474 -0
- package/templates/standard/dashboard/src/components/reports/CostTrendsReport.tsx +229 -0
- package/templates/standard/dashboard/src/components/reports/ErrorTrendsReport.tsx +244 -0
- package/templates/standard/dashboard/src/components/reports/ProjectHealthTable.tsx +251 -0
- package/templates/standard/dashboard/src/components/reports/WarningDigestsTable.tsx +298 -0
- package/templates/standard/dashboard/src/components/usage/react/UsageTable.tsx +385 -0
- package/templates/standard/dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx +305 -0
- package/templates/standard/dashboard/src/components/usage/unified/FeatureBudgets.tsx +472 -0
- package/templates/standard/dashboard/src/lib/errors/api.ts +84 -0
- package/templates/standard/dashboard/src/lib/errors/types.ts +75 -0
- package/templates/standard/dashboard/src/lib/infrastructure/api.ts +141 -0
- package/templates/standard/dashboard/src/lib/infrastructure/gatus.ts.hbs +112 -0
- package/templates/standard/dashboard/src/lib/services/proxy/index.ts +20 -0
- package/templates/standard/dashboard/src/lib/services/proxy/proxy.ts +244 -0
- package/templates/standard/dashboard/src/lib/services/proxy/types.ts +81 -0
- package/templates/standard/tests/integration/platform-sentinel.test.ts +497 -0
- package/templates/standard/tests/unit/cloudflare/alerting.test.ts +480 -0
- package/templates/standard/tests/unit/error-collector/dedup.test.ts +350 -0
- package/templates/standard/tests/unit/error-collector/github.test.ts +187 -0
package/README.md
CHANGED
|
@@ -70,8 +70,8 @@ Creates a `.platform-scaffold.json` manifest for projects scaffolded before v1.1
|
|
|
70
70
|
| Tier | Workers | What You Get | Est. Cost |
|
|
71
71
|
|------|---------|-------------|-----------|
|
|
72
72
|
| **Minimal** | 1 | Budget enforcement, circuit breakers, usage telemetry | ~$0/mo |
|
|
73
|
-
| **Standard** |
|
|
74
|
-
| **Full** |
|
|
73
|
+
| **Standard** | 5 | + Error collector (auto GitHub issues), gap detection sentinel, mapper, test client | ~$0/mo |
|
|
74
|
+
| **Full** | 11 | + AI pattern discovery, alert router, notifications, search, settings, auditor | ~$5/mo |
|
|
75
75
|
|
|
76
76
|
See [Tier Comparison](../../docs/admin-sdk/tiers.md) for a detailed breakdown of what each tier generates.
|
|
77
77
|
|
package/dist/templates.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Tier } from './prompts.js';
|
|
8
8
|
/** Single source of truth for the SDK version. */
|
|
9
|
-
export declare const SDK_VERSION = "2.
|
|
9
|
+
export declare const SDK_VERSION = "2.1.0";
|
|
10
10
|
/** Returns true if `to` is the same or higher tier than `from`. */
|
|
11
11
|
export declare function isTierUpgradeOrSame(from: Tier, to: Tier): boolean;
|
|
12
12
|
/** Check if a template file is a numbered migration (not seed.sql). */
|
package/dist/templates.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* All other files are copied verbatim.
|
|
6
6
|
*/
|
|
7
7
|
/** Single source of truth for the SDK version. */
|
|
8
|
-
export const SDK_VERSION = '2.
|
|
8
|
+
export const SDK_VERSION = '2.1.0';
|
|
9
9
|
/** Tier ordering for upgrade validation. */
|
|
10
10
|
const TIER_ORDER = { minimal: 0, standard: 1, full: 2 };
|
|
11
11
|
/** Returns true if `to` is the same or higher tier than `from`. */
|
|
@@ -169,6 +169,15 @@ const SHARED_FILES = [
|
|
|
169
169
|
{ src: 'shared/dashboard/src/pages/api/user/identity.ts', dest: 'dashboard/src/pages/api/user/identity.ts', template: false },
|
|
170
170
|
// Dashboard — shared tier: cloudflare lib (v1.6.0)
|
|
171
171
|
{ src: 'shared/dashboard/src/lib/cloudflare/costs.ts', dest: 'dashboard/src/lib/cloudflare/costs.ts', template: false },
|
|
172
|
+
// Dashboard — shared tier: cloudflare lib (v2.1.0)
|
|
173
|
+
{ src: 'shared/dashboard/src/lib/cloudflare/index.ts.hbs', dest: 'dashboard/src/lib/cloudflare/index.ts', template: true },
|
|
174
|
+
{ src: 'shared/dashboard/src/lib/cloudflare/d1.ts', dest: 'dashboard/src/lib/cloudflare/d1.ts', template: false },
|
|
175
|
+
{ src: 'shared/dashboard/src/lib/cloudflare/analytics.ts', dest: 'dashboard/src/lib/cloudflare/analytics.ts', template: false },
|
|
176
|
+
// Dashboard — shared tier: usage lib (v2.1.0)
|
|
177
|
+
{ src: 'shared/dashboard/src/lib/usage/index.ts.hbs', dest: 'dashboard/src/lib/usage/index.ts', template: true },
|
|
178
|
+
{ src: 'shared/dashboard/src/lib/usage/fetchWithDedup.ts', dest: 'dashboard/src/lib/usage/fetchWithDedup.ts', template: false },
|
|
179
|
+
// Dashboard — shared tier: infrastructure lib (v2.1.0)
|
|
180
|
+
{ src: 'shared/dashboard/src/lib/infrastructure/types.ts', dest: 'dashboard/src/lib/infrastructure/types.ts', template: false },
|
|
172
181
|
// Dashboard — shared tier: infrastructure components (v1.6.0)
|
|
173
182
|
{ src: 'shared/dashboard/src/components/infrastructure/InfrastructureStats.tsx', dest: 'dashboard/src/components/infrastructure/InfrastructureStats.tsx', template: false },
|
|
174
183
|
{ src: 'shared/dashboard/src/components/infrastructure/index.ts', dest: 'dashboard/src/components/infrastructure/index.ts', template: false },
|
|
@@ -206,6 +215,38 @@ const SHARED_FILES = [
|
|
|
206
215
|
// Dashboard — shared tier: additional UI components (v2.0.0)
|
|
207
216
|
{ src: 'shared/dashboard/src/components/ui/ErrorBoundary.tsx', dest: 'dashboard/src/components/ui/ErrorBoundary.tsx', template: false },
|
|
208
217
|
{ src: 'shared/dashboard/src/components/ui/PageShell.tsx', dest: 'dashboard/src/components/ui/PageShell.tsx', template: false },
|
|
218
|
+
// Dashboard — shared tier: usage/unified components (v2.1.0)
|
|
219
|
+
{ src: 'shared/dashboard/src/components/usage/unified/UnifiedShell.tsx', dest: 'dashboard/src/components/usage/unified/UnifiedShell.tsx', template: false },
|
|
220
|
+
{ src: 'shared/dashboard/src/components/usage/unified/HeroCardsRow.tsx', dest: 'dashboard/src/components/usage/unified/HeroCardsRow.tsx', template: false },
|
|
221
|
+
{ src: 'shared/dashboard/src/components/usage/unified/ResourceBreakdown.tsx', dest: 'dashboard/src/components/usage/unified/ResourceBreakdown.tsx', template: false },
|
|
222
|
+
{ src: 'shared/dashboard/src/components/usage/unified/LiveHeader.tsx', dest: 'dashboard/src/components/usage/unified/LiveHeader.tsx', template: false },
|
|
223
|
+
{ src: 'shared/dashboard/src/components/usage/unified/AlertBanner.tsx', dest: 'dashboard/src/components/usage/unified/AlertBanner.tsx', template: false },
|
|
224
|
+
{ src: 'shared/dashboard/src/components/usage/unified/Sparkline.tsx', dest: 'dashboard/src/components/usage/unified/Sparkline.tsx', template: false },
|
|
225
|
+
{ src: 'shared/dashboard/src/components/usage/unified/ProjectsTable.tsx', dest: 'dashboard/src/components/usage/unified/ProjectsTable.tsx', template: false },
|
|
226
|
+
{ src: 'shared/dashboard/src/components/usage/unified/types.ts', dest: 'dashboard/src/components/usage/unified/types.ts', template: false },
|
|
227
|
+
{ src: 'shared/dashboard/src/components/usage/unified/index.ts.hbs', dest: 'dashboard/src/components/usage/unified/index.ts', template: true },
|
|
228
|
+
// Dashboard — shared tier: usage/react components (v2.1.0)
|
|
229
|
+
{ src: 'shared/dashboard/src/components/usage/react/StatusBadge.tsx', dest: 'dashboard/src/components/usage/react/StatusBadge.tsx', template: false },
|
|
230
|
+
{ src: 'shared/dashboard/src/components/usage/react/UsageChart.tsx', dest: 'dashboard/src/components/usage/react/UsageChart.tsx', template: false },
|
|
231
|
+
{ src: 'shared/dashboard/src/components/usage/react/DashboardShell.tsx', dest: 'dashboard/src/components/usage/react/DashboardShell.tsx', template: false },
|
|
232
|
+
{ src: 'shared/dashboard/src/components/usage/react/types.ts', dest: 'dashboard/src/components/usage/react/types.ts', template: false },
|
|
233
|
+
{ src: 'shared/dashboard/src/components/usage/react/index.ts.hbs', dest: 'dashboard/src/components/usage/react/index.ts', template: true },
|
|
234
|
+
// Dashboard — shared tier: usage utilities (v2.1.0)
|
|
235
|
+
{ src: 'shared/dashboard/src/components/usage/transformers.ts', dest: 'dashboard/src/components/usage/transformers.ts', template: false },
|
|
236
|
+
// Dashboard — shared tier: reports components (v2.1.0)
|
|
237
|
+
{ src: 'shared/dashboard/src/components/reports/ReportInfoButton.tsx', dest: 'dashboard/src/components/reports/ReportInfoButton.tsx', template: false },
|
|
238
|
+
// Tests — shared tier (v2.1.0)
|
|
239
|
+
{ src: 'shared/tests/helpers/mock-storage.ts', dest: 'tests/helpers/mock-storage.ts', template: false },
|
|
240
|
+
{ src: 'shared/tests/unit/billing.test.ts', dest: 'tests/unit/billing.test.ts', template: false },
|
|
241
|
+
{ src: 'shared/tests/unit/economics.test.ts', dest: 'tests/unit/economics.test.ts', template: false },
|
|
242
|
+
{ src: 'shared/tests/unit/control.test.ts', dest: 'tests/unit/control.test.ts', template: false },
|
|
243
|
+
{ src: 'shared/tests/unit/cost-calculator.test.ts', dest: 'tests/unit/cost-calculator.test.ts', template: false },
|
|
244
|
+
{ src: 'shared/tests/unit/telemetry-sampling.test.ts', dest: 'tests/unit/telemetry-sampling.test.ts', template: false },
|
|
245
|
+
{ src: 'shared/tests/unit/cloudflare/graphql.test.ts', dest: 'tests/unit/cloudflare/graphql.test.ts', template: false },
|
|
246
|
+
{ src: 'shared/tests/unit/components/usage-transformers.test.ts', dest: 'tests/unit/components/usage-transformers.test.ts', template: false },
|
|
247
|
+
{ src: 'shared/tests/integration/platform-usage.test.ts', dest: 'tests/integration/platform-usage.test.ts', template: false },
|
|
248
|
+
{ src: 'shared/tests/integration/kv-cache.test.ts', dest: 'tests/integration/kv-cache.test.ts', template: false },
|
|
249
|
+
{ src: 'shared/tests/e2e/usage-api.test.ts', dest: 'tests/e2e/usage-api.test.ts', template: false },
|
|
209
250
|
// CI — contract check + security workflows (v2.0.0)
|
|
210
251
|
{ src: 'shared/.github/workflows/contract-check.yml.hbs', dest: '.github/workflows/contract-check.yml', template: true },
|
|
211
252
|
{ src: 'shared/.github/workflows/security.yml', dest: '.github/workflows/security.yml', template: false },
|
|
@@ -255,7 +296,14 @@ const STANDARD_FILES = [
|
|
|
255
296
|
{ src: 'standard/dashboard/src/components/errors/ErrorsTable.tsx', dest: 'dashboard/src/components/errors/ErrorsTable.tsx', template: false },
|
|
256
297
|
{ src: 'standard/dashboard/src/components/errors/ErrorStats.tsx', dest: 'dashboard/src/components/errors/ErrorStats.tsx', template: false },
|
|
257
298
|
{ src: 'standard/dashboard/src/components/errors/index.ts', dest: 'dashboard/src/components/errors/index.ts', template: false },
|
|
258
|
-
|
|
299
|
+
// Dashboard — standard tier: lib modules (v2.1.0) — replaces old errors.ts
|
|
300
|
+
{ src: 'standard/dashboard/src/lib/errors/types.ts', dest: 'dashboard/src/lib/errors/types.ts', template: false },
|
|
301
|
+
{ src: 'standard/dashboard/src/lib/errors/api.ts', dest: 'dashboard/src/lib/errors/api.ts', template: false },
|
|
302
|
+
{ src: 'standard/dashboard/src/lib/infrastructure/api.ts', dest: 'dashboard/src/lib/infrastructure/api.ts', template: false },
|
|
303
|
+
{ src: 'standard/dashboard/src/lib/infrastructure/gatus.ts.hbs', dest: 'dashboard/src/lib/infrastructure/gatus.ts', template: true },
|
|
304
|
+
{ src: 'standard/dashboard/src/lib/services/proxy/types.ts', dest: 'dashboard/src/lib/services/proxy/types.ts', template: false },
|
|
305
|
+
{ src: 'standard/dashboard/src/lib/services/proxy/proxy.ts', dest: 'dashboard/src/lib/services/proxy/proxy.ts', template: false },
|
|
306
|
+
{ src: 'standard/dashboard/src/lib/services/proxy/index.ts', dest: 'dashboard/src/lib/services/proxy/index.ts', template: false },
|
|
259
307
|
// Dashboard — standard tier: error detail API routes (v1.6.0)
|
|
260
308
|
{ src: 'standard/dashboard/src/pages/api/errors/[fingerprint].ts', dest: 'dashboard/src/pages/api/errors/[fingerprint].ts', template: false },
|
|
261
309
|
{ src: 'standard/dashboard/src/pages/api/errors/[fingerprint]/mute.ts', dest: 'dashboard/src/pages/api/errors/[fingerprint]/mute.ts', template: false },
|
|
@@ -270,6 +318,21 @@ const STANDARD_FILES = [
|
|
|
270
318
|
{ src: 'standard/tests/unit/error-collector/capture.test.ts', dest: 'tests/unit/error-collector/capture.test.ts', template: false },
|
|
271
319
|
// Dashboard — standard tier: audit history API route (v2.0.0)
|
|
272
320
|
{ src: 'standard/dashboard/src/pages/api/health/audit-history.ts', dest: 'dashboard/src/pages/api/health/audit-history.ts', template: false },
|
|
321
|
+
// Dashboard — standard tier: usage components (v2.1.0)
|
|
322
|
+
{ src: 'standard/dashboard/src/components/usage/react/UsageTable.tsx', dest: 'dashboard/src/components/usage/react/UsageTable.tsx', template: false },
|
|
323
|
+
{ src: 'standard/dashboard/src/components/usage/unified/FeatureBudgets.tsx', dest: 'dashboard/src/components/usage/unified/FeatureBudgets.tsx', template: false },
|
|
324
|
+
{ src: 'standard/dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx', dest: 'dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx', template: false },
|
|
325
|
+
// Dashboard — standard tier: reports components (v2.1.0)
|
|
326
|
+
{ src: 'standard/dashboard/src/components/reports/CircuitBreakerReport.tsx', dest: 'dashboard/src/components/reports/CircuitBreakerReport.tsx', template: false },
|
|
327
|
+
{ src: 'standard/dashboard/src/components/reports/ErrorTrendsReport.tsx', dest: 'dashboard/src/components/reports/ErrorTrendsReport.tsx', template: false },
|
|
328
|
+
{ src: 'standard/dashboard/src/components/reports/ProjectHealthTable.tsx', dest: 'dashboard/src/components/reports/ProjectHealthTable.tsx', template: false },
|
|
329
|
+
{ src: 'standard/dashboard/src/components/reports/CostTrendsReport.tsx', dest: 'dashboard/src/components/reports/CostTrendsReport.tsx', template: false },
|
|
330
|
+
{ src: 'standard/dashboard/src/components/reports/WarningDigestsTable.tsx', dest: 'dashboard/src/components/reports/WarningDigestsTable.tsx', template: false },
|
|
331
|
+
// Tests — standard tier (v2.1.0)
|
|
332
|
+
{ src: 'standard/tests/unit/cloudflare/alerting.test.ts', dest: 'tests/unit/cloudflare/alerting.test.ts', template: false },
|
|
333
|
+
{ src: 'standard/tests/unit/error-collector/dedup.test.ts', dest: 'tests/unit/error-collector/dedup.test.ts', template: false },
|
|
334
|
+
{ src: 'standard/tests/unit/error-collector/github.test.ts', dest: 'tests/unit/error-collector/github.test.ts', template: false },
|
|
335
|
+
{ src: 'standard/tests/integration/platform-sentinel.test.ts', dest: 'tests/integration/platform-sentinel.test.ts', template: false },
|
|
273
336
|
];
|
|
274
337
|
const FULL_FILES = [
|
|
275
338
|
// Additional migrations
|
|
@@ -363,6 +426,27 @@ const FULL_FILES = [
|
|
|
363
426
|
{ src: 'full/dashboard/src/components/reports/SdkAuditReport.tsx', dest: 'dashboard/src/components/reports/SdkAuditReport.tsx', template: false },
|
|
364
427
|
{ src: 'full/dashboard/src/components/reports/GapDetectionReport.tsx', dest: 'dashboard/src/components/reports/GapDetectionReport.tsx', template: false },
|
|
365
428
|
{ src: 'full/dashboard/src/components/reports/index.ts', dest: 'dashboard/src/components/reports/index.ts', template: false },
|
|
429
|
+
// Dashboard — full tier: lib modules (v2.1.0)
|
|
430
|
+
{ src: 'full/dashboard/src/lib/cloudflare/graphql.ts', dest: 'dashboard/src/lib/cloudflare/graphql.ts', template: false },
|
|
431
|
+
{ src: 'full/dashboard/src/lib/cloudflare/alerting.ts', dest: 'dashboard/src/lib/cloudflare/alerting.ts', template: false },
|
|
432
|
+
{ src: 'full/dashboard/src/lib/cloudflare/project-registry.ts', dest: 'dashboard/src/lib/cloudflare/project-registry.ts', template: false },
|
|
433
|
+
{ src: 'full/dashboard/src/lib/usage/allowance-config.ts.hbs', dest: 'dashboard/src/lib/usage/allowance-config.ts', template: true },
|
|
434
|
+
{ src: 'full/dashboard/src/lib/usage/providers.ts', dest: 'dashboard/src/lib/usage/providers.ts', template: false },
|
|
435
|
+
{ src: 'full/dashboard/src/lib/patterns/api.ts', dest: 'dashboard/src/lib/patterns/api.ts', template: false },
|
|
436
|
+
{ src: 'full/dashboard/src/lib/patterns/types.ts', dest: 'dashboard/src/lib/patterns/types.ts', template: false },
|
|
437
|
+
{ src: 'full/dashboard/src/lib/notifications/api.ts', dest: 'dashboard/src/lib/notifications/api.ts', template: false },
|
|
438
|
+
{ src: 'full/dashboard/src/lib/notifications/types.ts.hbs', dest: 'dashboard/src/lib/notifications/types.ts', template: true },
|
|
439
|
+
{ src: 'full/dashboard/src/lib/search/api.ts', dest: 'dashboard/src/lib/search/api.ts', template: false },
|
|
440
|
+
{ src: 'full/dashboard/src/lib/search/types.ts.hbs', dest: 'dashboard/src/lib/search/types.ts', template: true },
|
|
441
|
+
{ src: 'full/dashboard/src/lib/settings/api.ts.hbs', dest: 'dashboard/src/lib/settings/api.ts', template: true },
|
|
442
|
+
{ src: 'full/dashboard/src/lib/settings/types.ts.hbs', dest: 'dashboard/src/lib/settings/types.ts', template: true },
|
|
443
|
+
{ src: 'full/dashboard/src/lib/reports/types.ts', dest: 'dashboard/src/lib/reports/types.ts', template: false },
|
|
444
|
+
// Dashboard — full tier: usage components (v2.1.0)
|
|
445
|
+
{ src: 'full/dashboard/src/components/usage/AIModelBreakdown.tsx', dest: 'dashboard/src/components/usage/AIModelBreakdown.tsx', template: false },
|
|
446
|
+
{ src: 'full/dashboard/src/components/usage/unified/Recommendations.tsx', dest: 'dashboard/src/components/usage/unified/Recommendations.tsx', template: false },
|
|
447
|
+
// Dashboard — full tier: reports components (v2.1.0)
|
|
448
|
+
{ src: 'full/dashboard/src/components/reports/HealthTrendsReport.tsx', dest: 'dashboard/src/components/reports/HealthTrendsReport.tsx', template: false },
|
|
449
|
+
{ src: 'full/dashboard/src/components/reports/DigestStats.tsx', dest: 'dashboard/src/components/reports/DigestStats.tsx', template: false },
|
|
366
450
|
];
|
|
367
451
|
export function getFilesForTier(tier) {
|
|
368
452
|
const files = [...SHARED_FILES];
|
package/package.json
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digest Statistics Cards Component
|
|
3
|
+
* Shows warning digest summary stats and trends
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect } from 'react';
|
|
6
|
+
import type { DigestStats as DigestStatsType } from '../../lib/reports/types';
|
|
7
|
+
|
|
8
|
+
interface StatCardProps {
|
|
9
|
+
label: string;
|
|
10
|
+
value: number | string;
|
|
11
|
+
subValue?: string;
|
|
12
|
+
colour: string;
|
|
13
|
+
icon: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function StatCard({ label, value, subValue, colour, icon }: StatCardProps) {
|
|
17
|
+
return (
|
|
18
|
+
<div className={`bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 ${colour}`}>
|
|
19
|
+
<div className="flex items-center justify-between">
|
|
20
|
+
<div>
|
|
21
|
+
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">{label}</p>
|
|
22
|
+
<p className="text-2xl font-bold mt-1">{value}</p>
|
|
23
|
+
{subValue && (
|
|
24
|
+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">{subValue}</p>
|
|
25
|
+
)}
|
|
26
|
+
</div>
|
|
27
|
+
<span className="text-2xl">{icon}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function DigestStats() {
|
|
34
|
+
const [stats, setStats] = useState<DigestStatsType | null>(null);
|
|
35
|
+
const [loading, setLoading] = useState(true);
|
|
36
|
+
const [error, setError] = useState<string | null>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
async function fetchStats() {
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch('/api/reports/digests/stats');
|
|
42
|
+
if (!response.ok) throw new Error('Failed to fetch stats');
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
setStats(data);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
setError(e instanceof Error ? e.message : 'Unknown error');
|
|
47
|
+
} finally {
|
|
48
|
+
setLoading(false);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
fetchStats();
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
if (loading) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
|
57
|
+
{[...Array(6)].map((_, i) => (
|
|
58
|
+
<div key={i} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 animate-pulse">
|
|
59
|
+
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-16 mb-2"></div>
|
|
60
|
+
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-12"></div>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (error) {
|
|
68
|
+
return (
|
|
69
|
+
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 text-red-700 dark:text-red-300">
|
|
70
|
+
Error loading stats: {error}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!stats) return null;
|
|
76
|
+
|
|
77
|
+
// Calculate trend (comparing today vs yesterday)
|
|
78
|
+
const todayOcc = stats.todayDigests.occurrences;
|
|
79
|
+
const yesterdayOcc = stats.yesterdayDigests.occurrences;
|
|
80
|
+
const trend = yesterdayOcc > 0
|
|
81
|
+
? Math.round(((todayOcc - yesterdayOcc) / yesterdayOcc) * 100)
|
|
82
|
+
: todayOcc > 0 ? 100 : 0;
|
|
83
|
+
const trendText = trend > 0 ? `+${trend}%` : trend < 0 ? `${trend}%` : 'Same';
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="space-y-4">
|
|
87
|
+
{/* Main Stats */}
|
|
88
|
+
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
|
89
|
+
<StatCard
|
|
90
|
+
label="Today's Warnings"
|
|
91
|
+
value={stats.todayDigests.occurrences}
|
|
92
|
+
subValue={`${stats.todayDigests.count} digest(s)`}
|
|
93
|
+
colour="border-l-4 border-l-yellow-500"
|
|
94
|
+
icon="⚠️"
|
|
95
|
+
/>
|
|
96
|
+
<StatCard
|
|
97
|
+
label="Yesterday"
|
|
98
|
+
value={stats.yesterdayDigests.occurrences}
|
|
99
|
+
subValue={`${stats.yesterdayDigests.count} digest(s)`}
|
|
100
|
+
colour="border-l-4 border-l-gray-400"
|
|
101
|
+
icon="📅"
|
|
102
|
+
/>
|
|
103
|
+
<StatCard
|
|
104
|
+
label="Daily Trend"
|
|
105
|
+
value={trendText}
|
|
106
|
+
subValue={trend > 0 ? 'Increase' : trend < 0 ? 'Decrease' : 'No change'}
|
|
107
|
+
colour={`border-l-4 ${trend > 20 ? 'border-l-red-500' : trend < -20 ? 'border-l-green-500' : 'border-l-blue-500'}`}
|
|
108
|
+
icon={trend > 0 ? '📈' : trend < 0 ? '📉' : '➡️'}
|
|
109
|
+
/>
|
|
110
|
+
<StatCard
|
|
111
|
+
label="Total Digests"
|
|
112
|
+
value={stats.totalDigests}
|
|
113
|
+
colour="border-l-4 border-l-purple-500"
|
|
114
|
+
icon="📊"
|
|
115
|
+
/>
|
|
116
|
+
<StatCard
|
|
117
|
+
label="Total Occurrences"
|
|
118
|
+
value={stats.totalOccurrences.toLocaleString()}
|
|
119
|
+
colour="border-l-4 border-l-indigo-500"
|
|
120
|
+
icon="🔢"
|
|
121
|
+
/>
|
|
122
|
+
<StatCard
|
|
123
|
+
label="Active Scripts"
|
|
124
|
+
value={stats.byScript.length}
|
|
125
|
+
colour="border-l-4 border-l-teal-500"
|
|
126
|
+
icon="📜"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Top Warnings Summary */}
|
|
131
|
+
{stats.topWarnings.length > 0 && (
|
|
132
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
|
133
|
+
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Top Warning Types</h3>
|
|
134
|
+
<div className="space-y-2">
|
|
135
|
+
{stats.topWarnings.slice(0, 3).map((warning, i) => (
|
|
136
|
+
<div key={i} className="flex items-center justify-between text-sm">
|
|
137
|
+
<span className="text-gray-600 dark:text-gray-400 truncate max-w-md">
|
|
138
|
+
{warning.normalized_message.slice(0, 60)}
|
|
139
|
+
{warning.normalized_message.length > 60 && '...'}
|
|
140
|
+
</span>
|
|
141
|
+
<span className="text-gray-900 dark:text-white font-medium ml-2">
|
|
142
|
+
{warning.occurrences.toLocaleString()} ({warning.days_occurred}d)
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
))}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Trends Report Component
|
|
3
|
+
* Shows project health score trends over time
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
interface HealthTrend {
|
|
8
|
+
project: string;
|
|
9
|
+
audit_date: string;
|
|
10
|
+
composite_score: number;
|
|
11
|
+
sdk_score: number;
|
|
12
|
+
observability_score: number;
|
|
13
|
+
cost_score: number;
|
|
14
|
+
security_score: number;
|
|
15
|
+
trend: 'improving' | 'stable' | 'declining';
|
|
16
|
+
score_delta: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function StatCard({ label, value, colour, subValue }: { label: string; value: number | string; colour: string; subValue?: string }) {
|
|
20
|
+
return (
|
|
21
|
+
<div className={`bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 ${colour}`}>
|
|
22
|
+
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">{label}</p>
|
|
23
|
+
<p className="text-2xl font-bold mt-1">{value}</p>
|
|
24
|
+
{subValue && <p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">{subValue}</p>}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function TrendBadge({ trend, delta }: { trend: string; delta: number }) {
|
|
30
|
+
const config: Record<string, { colour: string; icon: string }> = {
|
|
31
|
+
improving: { colour: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300', icon: '↑' },
|
|
32
|
+
stable: { colour: 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300', icon: '→' },
|
|
33
|
+
declining: { colour: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300', icon: '↓' },
|
|
34
|
+
};
|
|
35
|
+
const { colour, icon } = config[trend] || config.stable;
|
|
36
|
+
return (
|
|
37
|
+
<span className={`px-2 py-0.5 text-xs font-semibold rounded inline-flex items-center gap-1 ${colour}`}>
|
|
38
|
+
{icon} {delta > 0 ? '+' : ''}{delta}
|
|
39
|
+
</span>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ScoreBar({ score, max = 5, label }: { score: number; max?: number; label: string }) {
|
|
44
|
+
const percentage = (score / max) * 100;
|
|
45
|
+
const colour = percentage >= 80 ? 'bg-green-500' : percentage >= 60 ? 'bg-yellow-500' : 'bg-red-500';
|
|
46
|
+
return (
|
|
47
|
+
<div className="flex items-center gap-2">
|
|
48
|
+
<span className="w-24 text-xs text-gray-600 dark:text-gray-400">{label}</span>
|
|
49
|
+
<div className="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
50
|
+
<div className={`h-full ${colour}`} style={{ width: `${percentage}%` }} />
|
|
51
|
+
</div>
|
|
52
|
+
<span className="w-8 text-xs text-gray-900 dark:text-white text-right">{score}/{max}</span>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function HealthTrendsReport() {
|
|
58
|
+
const [trends, setTrends] = useState<HealthTrend[]>([]);
|
|
59
|
+
const [loading, setLoading] = useState(true);
|
|
60
|
+
const [error, setError] = useState<string | null>(null);
|
|
61
|
+
const [days, setDays] = useState(30);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
async function fetchData() {
|
|
65
|
+
try {
|
|
66
|
+
setLoading(true);
|
|
67
|
+
const response = await fetch(`/api/usage/health-trends?days=${days}`);
|
|
68
|
+
if (!response.ok) throw new Error('Failed to fetch health trends');
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
|
|
71
|
+
// API returns { success, data: [{ project, trends: [{ date, compositeScore, rubricScores, trend, delta }] }] }
|
|
72
|
+
// Flatten nested project→trends structure into flat array
|
|
73
|
+
const apiData = data.data || [];
|
|
74
|
+
const flatTrends: HealthTrend[] = [];
|
|
75
|
+
for (const proj of apiData) {
|
|
76
|
+
for (const t of proj.trends || []) {
|
|
77
|
+
flatTrends.push({
|
|
78
|
+
project: proj.project,
|
|
79
|
+
audit_date: t.date,
|
|
80
|
+
composite_score: t.compositeScore,
|
|
81
|
+
sdk_score: t.rubricScores?.sdk ?? 0,
|
|
82
|
+
observability_score: t.rubricScores?.observability ?? 0,
|
|
83
|
+
cost_score: t.rubricScores?.cost ?? 0,
|
|
84
|
+
security_score: t.rubricScores?.security ?? 0,
|
|
85
|
+
trend: t.trend,
|
|
86
|
+
score_delta: t.delta,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
setTrends(flatTrends);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
setError(e instanceof Error ? e.message : 'Unknown error');
|
|
93
|
+
} finally {
|
|
94
|
+
setLoading(false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
fetchData();
|
|
98
|
+
}, [days]);
|
|
99
|
+
|
|
100
|
+
if (loading) {
|
|
101
|
+
return (
|
|
102
|
+
<div className="space-y-4">
|
|
103
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
104
|
+
{[...Array(4)].map((_, i) => (
|
|
105
|
+
<div key={i} className="bg-white dark:bg-gray-800 rounded-lg border p-4 animate-pulse">
|
|
106
|
+
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-16 mb-2"></div>
|
|
107
|
+
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-12"></div>
|
|
108
|
+
</div>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (error) {
|
|
116
|
+
return (
|
|
117
|
+
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 text-red-700 dark:text-red-300">
|
|
118
|
+
Error loading health trends: {error}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const improving = trends.filter(t => t.trend === 'improving').length;
|
|
124
|
+
const stable = trends.filter(t => t.trend === 'stable').length;
|
|
125
|
+
const declining = trends.filter(t => t.trend === 'declining').length;
|
|
126
|
+
const avgScore = trends.length > 0
|
|
127
|
+
? Math.round(trends.reduce((sum, t) => sum + t.composite_score, 0) / trends.length)
|
|
128
|
+
: 0;
|
|
129
|
+
|
|
130
|
+
// Group by project for latest trends
|
|
131
|
+
const latestByProject = new Map<string, HealthTrend>();
|
|
132
|
+
trends.forEach(t => {
|
|
133
|
+
if (!latestByProject.has(t.project) || t.audit_date > latestByProject.get(t.project)!.audit_date) {
|
|
134
|
+
latestByProject.set(t.project, t);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
const latestTrends = Array.from(latestByProject.values()).sort((a, b) => b.composite_score - a.composite_score);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="space-y-6">
|
|
141
|
+
{/* Filter */}
|
|
142
|
+
<div className="flex items-center gap-4">
|
|
143
|
+
<label className="text-sm text-gray-600 dark:text-gray-400">Time range:</label>
|
|
144
|
+
<select
|
|
145
|
+
value={days}
|
|
146
|
+
onChange={(e) => setDays(Number(e.target.value))}
|
|
147
|
+
className="px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-sm"
|
|
148
|
+
>
|
|
149
|
+
<option value={7}>Last 7 days</option>
|
|
150
|
+
<option value={30}>Last 30 days</option>
|
|
151
|
+
<option value={90}>Last 90 days</option>
|
|
152
|
+
</select>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Stats */}
|
|
156
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
157
|
+
<StatCard label="Improving" value={improving} colour="border-l-4 border-l-green-500" />
|
|
158
|
+
<StatCard label="Stable" value={stable} colour="border-l-4 border-l-gray-400" />
|
|
159
|
+
<StatCard label="Declining" value={declining} colour="border-l-4 border-l-red-500" />
|
|
160
|
+
<StatCard label="Avg Score" value={avgScore} colour="border-l-4 border-l-purple-500" />
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{/* Project Health Cards */}
|
|
164
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
165
|
+
{latestTrends.map((t) => (
|
|
166
|
+
<div key={t.project} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
|
167
|
+
<div className="flex items-center justify-between mb-3">
|
|
168
|
+
<h3 className="font-semibold text-gray-900 dark:text-white">{t.project}</h3>
|
|
169
|
+
<TrendBadge trend={t.trend} delta={t.score_delta} />
|
|
170
|
+
</div>
|
|
171
|
+
<div className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
|
172
|
+
{t.composite_score}<span className="text-sm font-normal text-gray-500">/100</span>
|
|
173
|
+
</div>
|
|
174
|
+
<div className="space-y-2">
|
|
175
|
+
<ScoreBar score={t.sdk_score} label="SDK" />
|
|
176
|
+
<ScoreBar score={t.observability_score} label="Observability" />
|
|
177
|
+
<ScoreBar score={t.cost_score} label="Cost" />
|
|
178
|
+
<ScoreBar score={t.security_score} label="Security" />
|
|
179
|
+
</div>
|
|
180
|
+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-3">Last audit: {t.audit_date}</p>
|
|
181
|
+
</div>
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{latestTrends.length === 0 && (
|
|
186
|
+
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg border p-8 text-center text-gray-500 dark:text-gray-400">
|
|
187
|
+
No health trend data available. Run the platform auditor to generate health scores.
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|