@littlebearapps/platform-admin-sdk 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +4 -7
  2. package/dist/templates.d.ts +1 -1
  3. package/dist/templates.js +206 -4
  4. package/package.json +1 -1
  5. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  6. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  7. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  8. package/templates/full/dashboard/src/components/reports/DigestStats.tsx +151 -0
  9. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  10. package/templates/full/dashboard/src/components/reports/HealthTrendsReport.tsx +192 -0
  11. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  12. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  13. package/templates/full/dashboard/src/components/usage/AIModelBreakdown.tsx +364 -0
  14. package/templates/full/dashboard/src/components/usage/unified/Recommendations.tsx +149 -0
  15. package/templates/full/dashboard/src/lib/cloudflare/alerting.ts +486 -0
  16. package/templates/full/dashboard/src/lib/cloudflare/graphql.ts +4785 -0
  17. package/templates/full/dashboard/src/lib/cloudflare/project-registry.ts +451 -0
  18. package/templates/full/dashboard/src/lib/notifications/api.ts +197 -0
  19. package/templates/full/dashboard/src/lib/notifications/types.ts.hbs +97 -0
  20. package/templates/full/dashboard/src/lib/patterns/api.ts +120 -0
  21. package/templates/full/dashboard/src/lib/patterns/types.ts +127 -0
  22. package/templates/full/dashboard/src/lib/reports/types.ts +231 -0
  23. package/templates/full/dashboard/src/lib/search/api.ts +258 -0
  24. package/templates/full/dashboard/src/lib/search/types.ts.hbs +115 -0
  25. package/templates/full/dashboard/src/lib/settings/api.ts.hbs +201 -0
  26. package/templates/full/dashboard/src/lib/settings/types.ts.hbs +104 -0
  27. package/templates/full/dashboard/src/lib/usage/allowance-config.ts.hbs +547 -0
  28. package/templates/full/dashboard/src/lib/usage/providers.ts +331 -0
  29. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  30. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  31. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  32. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  33. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  34. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  35. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  36. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  37. package/templates/full/dashboard/src/pages/map.astro +561 -0
  38. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  39. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  40. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  41. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  42. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  43. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  44. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  45. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  46. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  47. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  48. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  49. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  50. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  51. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  52. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  53. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  54. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  55. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  56. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  57. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  58. package/templates/shared/dashboard/src/components/reports/ReportInfoButton.tsx +98 -0
  59. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  60. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  61. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  62. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  63. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  64. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  65. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  66. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  67. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  68. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  69. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  70. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  71. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  72. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  73. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  74. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  75. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  76. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  77. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  78. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  79. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  80. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  81. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  82. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  83. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  84. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  85. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  86. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  87. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  88. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  89. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  90. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  91. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  92. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  93. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  94. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  95. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  96. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  97. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  98. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  99. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  100. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  101. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  102. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  103. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  104. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  105. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  106. package/templates/shared/dashboard/src/components/usage/react/DashboardShell.tsx +263 -0
  107. package/templates/shared/dashboard/src/components/usage/react/StatusBadge.tsx +77 -0
  108. package/templates/shared/dashboard/src/components/usage/react/UsageChart.tsx +391 -0
  109. package/templates/shared/dashboard/src/components/usage/react/index.ts.hbs +30 -0
  110. package/templates/shared/dashboard/src/components/usage/react/types.ts +137 -0
  111. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  112. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  113. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  114. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  115. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  116. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  117. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  118. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  119. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  120. package/templates/shared/dashboard/src/components/usage/transformers.ts +478 -0
  121. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  122. package/templates/shared/dashboard/src/components/usage/unified/AlertBanner.tsx +172 -0
  123. package/templates/shared/dashboard/src/components/usage/unified/HeroCardsRow.tsx +757 -0
  124. package/templates/shared/dashboard/src/components/usage/unified/LiveHeader.tsx +169 -0
  125. package/templates/shared/dashboard/src/components/usage/unified/ProjectsTable.tsx +448 -0
  126. package/templates/shared/dashboard/src/components/usage/unified/ResourceBreakdown.tsx +236 -0
  127. package/templates/shared/dashboard/src/components/usage/unified/Sparkline.tsx +127 -0
  128. package/templates/shared/dashboard/src/components/usage/unified/UnifiedShell.tsx +893 -0
  129. package/templates/shared/dashboard/src/components/usage/unified/index.ts.hbs +50 -0
  130. package/templates/shared/dashboard/src/components/usage/unified/types.ts +416 -0
  131. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  132. package/templates/shared/dashboard/src/lib/cloudflare/analytics.ts +310 -0
  133. package/templates/shared/dashboard/src/lib/cloudflare/d1.ts +55 -0
  134. package/templates/shared/dashboard/src/lib/cloudflare/index.ts.hbs +120 -0
  135. package/templates/shared/dashboard/src/lib/infrastructure/types.ts +116 -0
  136. package/templates/shared/dashboard/src/lib/usage/fetchWithDedup.ts +101 -0
  137. package/templates/shared/dashboard/src/lib/usage/index.ts.hbs +12 -0
  138. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  139. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  140. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  141. package/templates/shared/tests/e2e/usage-api.test.ts +909 -0
  142. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  143. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  144. package/templates/shared/tests/helpers/mock-storage.ts +166 -0
  145. package/templates/shared/tests/integration/kv-cache.test.ts +252 -0
  146. package/templates/shared/tests/integration/platform-usage.test.ts +956 -0
  147. package/templates/shared/tests/unit/billing.test.ts +331 -0
  148. package/templates/shared/tests/unit/cloudflare/graphql.test.ts +217 -0
  149. package/templates/shared/tests/unit/components/usage-transformers.test.ts +473 -0
  150. package/templates/shared/tests/unit/control.test.ts +226 -0
  151. package/templates/shared/tests/unit/cost-calculator.test.ts +141 -0
  152. package/templates/shared/tests/unit/economics.test.ts +365 -0
  153. package/templates/shared/tests/unit/telemetry-sampling.test.ts +401 -0
  154. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  155. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  156. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  157. package/templates/standard/dashboard/src/components/reports/CircuitBreakerReport.tsx +474 -0
  158. package/templates/standard/dashboard/src/components/reports/CostTrendsReport.tsx +229 -0
  159. package/templates/standard/dashboard/src/components/reports/ErrorTrendsReport.tsx +244 -0
  160. package/templates/standard/dashboard/src/components/reports/ProjectHealthTable.tsx +251 -0
  161. package/templates/standard/dashboard/src/components/reports/WarningDigestsTable.tsx +298 -0
  162. package/templates/standard/dashboard/src/components/usage/react/UsageTable.tsx +385 -0
  163. package/templates/standard/dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx +305 -0
  164. package/templates/standard/dashboard/src/components/usage/unified/FeatureBudgets.tsx +472 -0
  165. package/templates/standard/dashboard/src/lib/errors/api.ts +84 -0
  166. package/templates/standard/dashboard/src/lib/errors/types.ts +75 -0
  167. package/templates/standard/dashboard/src/lib/infrastructure/api.ts +141 -0
  168. package/templates/standard/dashboard/src/lib/infrastructure/gatus.ts.hbs +112 -0
  169. package/templates/standard/dashboard/src/lib/services/proxy/index.ts +20 -0
  170. package/templates/standard/dashboard/src/lib/services/proxy/proxy.ts +244 -0
  171. package/templates/standard/dashboard/src/lib/services/proxy/types.ts +81 -0
  172. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  173. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  174. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  175. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  176. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  177. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  178. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  179. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  180. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  181. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
  182. package/templates/standard/tests/integration/platform-sentinel.test.ts +497 -0
  183. package/templates/standard/tests/unit/cloudflare/alerting.test.ts +480 -0
  184. package/templates/standard/tests/unit/error-collector/dedup.test.ts +350 -0
  185. package/templates/standard/tests/unit/error-collector/github.test.ts +187 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Cloudflare Observability Library
3
+ *
4
+ * Provides unified access to Cloudflare metrics, costs, and analytics.
5
+ *
6
+ * @module cloudflare
7
+ */
8
+
9
+ // Cost calculator and types
10
+ export {
11
+ CF_PRICING,
12
+ CF_FREE_LIMITS,
13
+ PROJECT_PATTERNS,
14
+ DEFAULT_ALERT_THRESHOLDS,
15
+ identifyProject,
16
+ calculateMonthlyCosts,
17
+ calculateProjectCosts,
18
+ calculateDailyCosts,
19
+ getThresholdLevel,
20
+ analyseThresholds,
21
+ mergeThresholds,
22
+ formatNumber,
23
+ formatCurrency,
24
+ type CostBreakdown,
25
+ type ProjectCostBreakdown,
26
+ type DailyUsageMetrics,
27
+ type ThresholdLevel,
28
+ type ThresholdWarning,
29
+ type ThresholdAnalysis,
30
+ type AlertServiceType,
31
+ type ServiceThreshold,
32
+ type AlertThresholds,
33
+ } from './costs';
34
+
35
+ // D1 helpers
36
+ export { getSystemHealth, type ProjectHealthMap, type HealthCheckRecord } from './d1';
37
+
38
+ // Analytics Engine client
39
+ export {
40
+ queryAnalyticsEngine,
41
+ selectFromAnalytics,
42
+ AnalyticsEngineError,
43
+ type AnalyticsEngineConfig,
44
+ type QueryResult,
45
+ type AnalyticsEngineErrorCode,
46
+ type AnalyticsEngineResponse,
47
+ } from './analytics';
48
+
49
+ {{#if isFull}}
50
+ // GraphQL client and types (Full tier)
51
+ export {
52
+ CloudflareGraphQL,
53
+ type TimePeriod,
54
+ type DateRange,
55
+ type CustomDateRangeParams,
56
+ type CompareMode,
57
+ type WorkersMetrics,
58
+ type D1Metrics,
59
+ type KVMetrics,
60
+ type R2Metrics,
61
+ type DOMetrics,
62
+ type VectorizeInfo,
63
+ type AIGatewayMetrics,
64
+ type PagesMetrics,
65
+ type SparklinePoint,
66
+ type SparklineData,
67
+ type WorkersErrorBreakdown,
68
+ type QueuesMetrics,
69
+ type CacheAnalytics,
70
+ type PeriodComparison,
71
+ type AccountUsage,
72
+ type EnhancedAccountUsage,
73
+ type WorkersAIMetrics,
74
+ type WorkersAISummary,
75
+ type AIGatewaySummary,
76
+ type DailyCostBreakdown,
77
+ type DailyCostData,
78
+ type CloudflareSubscription,
79
+ type WorkersPaidPlanInclusions,
80
+ type CloudflareBillingProfile,
81
+ type CloudflareAccountSubscriptions,
82
+ } from './graphql';
83
+
84
+ // Alerting service and types
85
+ export {
86
+ getSeverityColour,
87
+ getSeverityEmoji,
88
+ formatPercentage,
89
+ generateAlertKey,
90
+ shouldSendAlert,
91
+ buildSlackMessage,
92
+ buildSummarySlackMessage,
93
+ sendSlackAlert,
94
+ evaluateWarning,
95
+ buildEmailHtml,
96
+ buildEmailText,
97
+ type CostSpikeAlert,
98
+ type AlertResult,
99
+ type SlackMessage,
100
+ } from './alerting';
101
+
102
+ // Project registry - D1-backed resource-to-project mapping
103
+ export {
104
+ getProjects,
105
+ getProject,
106
+ identifyProjectFromRegistry,
107
+ getProjectResources,
108
+ getProjectResourcesByType,
109
+ getResourceCountsByProject,
110
+ getResourceCountByType,
111
+ upsertResourceMapping,
112
+ deleteResourceMapping,
113
+ createProject,
114
+ updateProjectStatus,
115
+ clearRegistryCache,
116
+ type Project,
117
+ type ResourceMapping,
118
+ type ResourceType,
119
+ } from './project-registry';
120
+ {{/if}}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Infrastructure Types
3
+ * TypeScript types for infrastructure monitoring dashboard
4
+ */
5
+
6
+ export type ServiceStatus = 'deployed' | 'development' | 'deprecated' | 'paused';
7
+ export type ServiceType = 'worker' | 'vps-app' | 'd1' | 'kv' | 'r2' | 'vectorize' | 'queue';
8
+ export type MonitorStatus = 'up' | 'down' | 'paused' | 'unknown';
9
+ export type HealthcheckStatus = 'up' | 'down' | 'grace' | 'paused' | 'new';
10
+
11
+ export interface Service {
12
+ id: string;
13
+ name: string;
14
+ type: ServiceType;
15
+ status: ServiceStatus;
16
+ project: string;
17
+ scriptName?: string;
18
+ schedule?: string | null;
19
+ lastDeployed?: number | null;
20
+ endpoint?: string;
21
+ }
22
+
23
+ export interface ServiceRegistryStats {
24
+ total: number;
25
+ byStatus: Record<ServiceStatus, number>;
26
+ byType: Record<ServiceType, number>;
27
+ byProject: Record<string, number>;
28
+ }
29
+
30
+ export interface UptimeMonitor {
31
+ id: string;
32
+ name: string;
33
+ url: string;
34
+ status: MonitorStatus;
35
+ uptimeRatio: number; // Last 7 days (from Gatus)
36
+ responseTime: number; // Latest ms
37
+ lastCheckAt: number;
38
+ createdAt: number;
39
+ }
40
+
41
+ export interface HealthcheckJob {
42
+ id: string;
43
+ name: string;
44
+ slug: string;
45
+ status: HealthcheckStatus;
46
+ lastPing: number | null;
47
+ nextPing: number | null;
48
+ period: number; // seconds
49
+ grace: number; // seconds
50
+ nPings: number;
51
+ tags: string[];
52
+ // Optional flip summary (populated by enhanced API)
53
+ recentFlips?: FlipEvent[];
54
+ flipsToday?: number;
55
+ lastFailure?: number | null;
56
+ }
57
+
58
+ /**
59
+ * A flip represents a status change (up→down or down→up)
60
+ * Retrieved from Gatus events API
61
+ */
62
+ export interface FlipEvent {
63
+ timestamp: number; // Unix timestamp
64
+ up: boolean; // true = went up, false = went down
65
+ }
66
+
67
+ /**
68
+ * A response time data point from Gatus
69
+ */
70
+ export interface ResponseTimeDataPoint {
71
+ timestamp: number; // Unix timestamp
72
+ value: number; // Response time in ms
73
+ }
74
+
75
+ /**
76
+ * Response time stats summary
77
+ */
78
+ export interface ResponseTimeStats {
79
+ avg: number;
80
+ min: number;
81
+ max: number;
82
+ trend: 'improving' | 'stable' | 'degrading';
83
+ }
84
+
85
+ export interface Alert {
86
+ id: string;
87
+ type: string;
88
+ severity: 'info' | 'warning' | 'critical';
89
+ source: string;
90
+ message: string;
91
+ createdAt: number;
92
+ acknowledgedAt: number | null;
93
+ resolvedAt: number | null;
94
+ metadata?: Record<string, unknown>;
95
+ }
96
+
97
+ export interface InfrastructureStats {
98
+ services: ServiceRegistryStats;
99
+ monitors: {
100
+ total: number;
101
+ up: number;
102
+ down: number;
103
+ averageUptime: number;
104
+ };
105
+ healthchecks: {
106
+ total: number;
107
+ up: number;
108
+ down: number;
109
+ grace: number;
110
+ };
111
+ alerts: {
112
+ total: number;
113
+ unacknowledged: number;
114
+ critical: number;
115
+ };
116
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Fetch with Request Deduplication
3
+ *
4
+ * Prevents duplicate API calls by:
5
+ * 1. Deduplicating concurrent requests to the same URL
6
+ * 2. Caching responses for a short TTL
7
+ *
8
+ * This is used by the unified dashboard components to prevent
9
+ * multiple components from making redundant API calls.
10
+ */
11
+
12
+ interface CacheEntry<T> {
13
+ data: T;
14
+ expiresAt: number;
15
+ }
16
+
17
+ // In-flight request tracking (prevents concurrent duplicate requests)
18
+ const pendingRequests = new Map<string, Promise<Response>>();
19
+
20
+ // Response cache with TTL
21
+ const responseCache = new Map<string, CacheEntry<unknown>>();
22
+
23
+ // Default cache TTL: 5 seconds (short enough to stay fresh, long enough to dedupe)
24
+ const DEFAULT_CACHE_TTL = 5000;
25
+
26
+ /**
27
+ * Fetch with automatic request deduplication and short-term caching
28
+ */
29
+ export async function fetchWithDedup<T>(
30
+ url: string,
31
+ options: RequestInit = {},
32
+ cacheTtl: number = DEFAULT_CACHE_TTL
33
+ ): Promise<T> {
34
+ const cacheKey = `${options.method || 'GET'}:${url}`;
35
+
36
+ // Check response cache first
37
+ const cached = responseCache.get(cacheKey);
38
+ if (cached && Date.now() < cached.expiresAt) {
39
+ return cached.data as T;
40
+ }
41
+
42
+ // Check for in-flight request
43
+ const pending = pendingRequests.get(cacheKey);
44
+ if (pending) {
45
+ const response = await pending;
46
+ const data = await response.clone().json();
47
+ return data as T;
48
+ }
49
+
50
+ // Create new request
51
+ const request = fetch(url, {
52
+ credentials: 'include',
53
+ ...options,
54
+ });
55
+
56
+ pendingRequests.set(cacheKey, request);
57
+
58
+ try {
59
+ const response = await request;
60
+ pendingRequests.delete(cacheKey);
61
+
62
+ if (!response.ok) {
63
+ throw new Error(`HTTP ${response.status}`);
64
+ }
65
+
66
+ const data = await response.json();
67
+
68
+ // Cache the response
69
+ responseCache.set(cacheKey, {
70
+ data,
71
+ expiresAt: Date.now() + cacheTtl,
72
+ });
73
+
74
+ return data as T;
75
+ } catch (error) {
76
+ pendingRequests.delete(cacheKey);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Clear the response cache (useful for forced refresh)
83
+ */
84
+ export function clearFetchCache(urlPattern?: string): void {
85
+ if (urlPattern) {
86
+ for (const key of responseCache.keys()) {
87
+ if (key.includes(urlPattern)) {
88
+ responseCache.delete(key);
89
+ }
90
+ }
91
+ } else {
92
+ responseCache.clear();
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Check if a URL is currently being fetched
98
+ */
99
+ export function isFetching(url: string): boolean {
100
+ return pendingRequests.has(`GET:${url}`);
101
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Usage Library Exports
3
+ *
4
+ * Centralised exports for usage-related configuration and utilities.
5
+ */
6
+
7
+ export * from './fetchWithDedup';
8
+
9
+ {{#if isFull}}
10
+ export * from './allowance-config';
11
+ export * from './providers';
12
+ {{/if}}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * AI Model Usage Breakdown API Endpoint
3
+ *
4
+ * Returns per-model usage and cost data from:
5
+ * - Workers AI (workersai_model_daily)
6
+ * - AI Gateway (aigateway_model_daily)
7
+ *
8
+ * @module pages/api/usage/ai-models
9
+ */
10
+
11
+ import type { APIRoute } from 'astro';
12
+ import type { D1Database } from '@cloudflare/workers-types';
13
+
14
+ // =============================================================================
15
+ // TYPES
16
+ // =============================================================================
17
+
18
+ interface WorkersAIModel {
19
+ model: string;
20
+ project: string;
21
+ requests: number;
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ costUsd: number;
25
+ }
26
+
27
+ interface AIGatewayModel {
28
+ provider: string;
29
+ model: string;
30
+ gatewayId: string;
31
+ requests: number;
32
+ cachedRequests: number;
33
+ tokensIn: number;
34
+ tokensOut: number;
35
+ costUsd: number;
36
+ }
37
+
38
+ interface SuccessResponse {
39
+ success: true;
40
+ workersAI: {
41
+ models: WorkersAIModel[];
42
+ totalCostUsd: number;
43
+ totalRequests: number;
44
+ };
45
+ aiGateway: {
46
+ models: AIGatewayModel[];
47
+ totalCostUsd: number;
48
+ totalRequests: number;
49
+ byProvider: Record<string, { requests: number; costUsd: number }>;
50
+ };
51
+ period: { startDate: string; endDate: string };
52
+ responseTimeMs: number;
53
+ }
54
+
55
+ interface ErrorResponse {
56
+ success: false;
57
+ error: string;
58
+ code: string;
59
+ }
60
+
61
+ // =============================================================================
62
+ // HANDLER
63
+ // =============================================================================
64
+
65
+ export const GET: APIRoute = async ({ locals, url }) => {
66
+ const startTime = Date.now();
67
+
68
+ const env = locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined;
69
+
70
+ if (!env?.PLATFORM_DB) {
71
+ const response: ErrorResponse = {
72
+ success: false,
73
+ error: 'Database binding not available',
74
+ code: 'BINDING_ERROR',
75
+ };
76
+ return new Response(JSON.stringify(response), {
77
+ status: 503,
78
+ headers: { 'Content-Type': 'application/json' },
79
+ });
80
+ }
81
+
82
+ // Parse query params
83
+ const periodParam = url.searchParams.get('period') ?? '30d';
84
+ const now = new Date();
85
+ let startDate: string;
86
+
87
+ if (periodParam === '7d') {
88
+ const d = new Date(now);
89
+ d.setDate(d.getDate() - 7);
90
+ startDate = d.toISOString().slice(0, 10);
91
+ } else if (periodParam === '24h') {
92
+ startDate = now.toISOString().slice(0, 10);
93
+ } else {
94
+ const d = new Date(now);
95
+ d.setDate(d.getDate() - 30);
96
+ startDate = d.toISOString().slice(0, 10);
97
+ }
98
+
99
+ const endDate = now.toISOString().slice(0, 10);
100
+
101
+ try {
102
+ // Query Workers AI model usage
103
+ const workersAIResult = await env.PLATFORM_DB.prepare(
104
+ `
105
+ SELECT
106
+ model,
107
+ project,
108
+ SUM(requests) as requests,
109
+ SUM(input_tokens) as input_tokens,
110
+ SUM(output_tokens) as output_tokens,
111
+ SUM(cost_usd) as cost_usd
112
+ FROM workersai_model_daily
113
+ WHERE snapshot_date >= ? AND snapshot_date <= ?
114
+ GROUP BY model, project
115
+ ORDER BY cost_usd DESC
116
+ `
117
+ )
118
+ .bind(startDate, endDate)
119
+ .all<{
120
+ model: string;
121
+ project: string;
122
+ requests: number;
123
+ input_tokens: number;
124
+ output_tokens: number;
125
+ cost_usd: number;
126
+ }>();
127
+
128
+ // Query AI Gateway model usage
129
+ const aiGatewayResult = await env.PLATFORM_DB.prepare(
130
+ `
131
+ SELECT
132
+ provider,
133
+ model,
134
+ gateway_id,
135
+ SUM(requests) as requests,
136
+ SUM(cached_requests) as cached_requests,
137
+ SUM(tokens_in) as tokens_in,
138
+ SUM(tokens_out) as tokens_out,
139
+ SUM(cost_usd) as cost_usd
140
+ FROM aigateway_model_daily
141
+ WHERE snapshot_date >= ? AND snapshot_date <= ?
142
+ GROUP BY provider, model, gateway_id
143
+ ORDER BY cost_usd DESC
144
+ `
145
+ )
146
+ .bind(startDate, endDate)
147
+ .all<{
148
+ provider: string;
149
+ model: string;
150
+ gateway_id: string;
151
+ requests: number;
152
+ cached_requests: number;
153
+ tokens_in: number;
154
+ tokens_out: number;
155
+ cost_usd: number;
156
+ }>();
157
+
158
+ // Transform Workers AI results
159
+ const workersAIModels: WorkersAIModel[] = (workersAIResult.results ?? []).map((row) => ({
160
+ model: row.model,
161
+ project: row.project,
162
+ requests: row.requests ?? 0,
163
+ inputTokens: row.input_tokens ?? 0,
164
+ outputTokens: row.output_tokens ?? 0,
165
+ costUsd: row.cost_usd ?? 0,
166
+ }));
167
+
168
+ const workersAITotalCost = workersAIModels.reduce((sum, m) => sum + m.costUsd, 0);
169
+ const workersAITotalRequests = workersAIModels.reduce((sum, m) => sum + m.requests, 0);
170
+
171
+ // Transform AI Gateway results
172
+ const aiGatewayModels: AIGatewayModel[] = (aiGatewayResult.results ?? []).map((row) => ({
173
+ provider: row.provider,
174
+ model: row.model,
175
+ gatewayId: row.gateway_id,
176
+ requests: row.requests ?? 0,
177
+ cachedRequests: row.cached_requests ?? 0,
178
+ tokensIn: row.tokens_in ?? 0,
179
+ tokensOut: row.tokens_out ?? 0,
180
+ costUsd: row.cost_usd ?? 0,
181
+ }));
182
+
183
+ const aiGatewayTotalCost = aiGatewayModels.reduce((sum, m) => sum + m.costUsd, 0);
184
+ const aiGatewayTotalRequests = aiGatewayModels.reduce((sum, m) => sum + m.requests, 0);
185
+
186
+ // Group AI Gateway by provider
187
+ const byProvider: Record<string, { requests: number; costUsd: number }> = {};
188
+ for (const m of aiGatewayModels) {
189
+ if (!byProvider[m.provider]) {
190
+ byProvider[m.provider] = { requests: 0, costUsd: 0 };
191
+ }
192
+ byProvider[m.provider].requests += m.requests;
193
+ byProvider[m.provider].costUsd += m.costUsd;
194
+ }
195
+
196
+ const response: SuccessResponse = {
197
+ success: true,
198
+ workersAI: {
199
+ models: workersAIModels,
200
+ totalCostUsd: Math.round(workersAITotalCost * 100) / 100,
201
+ totalRequests: workersAITotalRequests,
202
+ },
203
+ aiGateway: {
204
+ models: aiGatewayModels,
205
+ totalCostUsd: Math.round(aiGatewayTotalCost * 100) / 100,
206
+ totalRequests: aiGatewayTotalRequests,
207
+ byProvider,
208
+ },
209
+ period: { startDate, endDate },
210
+ responseTimeMs: Date.now() - startTime,
211
+ };
212
+
213
+ return new Response(JSON.stringify(response), {
214
+ status: 200,
215
+ headers: {
216
+ 'Content-Type': 'application/json',
217
+ 'Cache-Control': 'public, max-age=300',
218
+ },
219
+ });
220
+ } catch (error) {
221
+ const errorMessage = error instanceof Error ? error.message : String(error);
222
+ console.error('[AI-MODELS] Error:', errorMessage);
223
+
224
+ const response: ErrorResponse = {
225
+ success: false,
226
+ error: 'Failed to fetch AI model usage',
227
+ code: 'INTERNAL_ERROR',
228
+ };
229
+
230
+ return new Response(JSON.stringify(response), {
231
+ status: 500,
232
+ headers: { 'Content-Type': 'application/json' },
233
+ });
234
+ }
235
+ };