@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,283 @@
1
+ /**
2
+ * Usage Dashboard Shared Types
3
+ *
4
+ * Centralised type definitions for usage dashboard components.
5
+ * Part of task-17 (Enhanced Cloudflare Usage Dashboard).
6
+ */
7
+
8
+ export type ResourceType =
9
+ | 'worker'
10
+ | 'd1'
11
+ | 'kv'
12
+ | 'r2'
13
+ | 'vectorize'
14
+ | 'pages'
15
+ | 'queues'
16
+ | 'workflows'
17
+ | 'do'
18
+ | 'ai-gateway';
19
+
20
+ export type ResourceStatus = 'healthy' | 'warning' | 'high' | 'critical';
21
+
22
+ export type SortDirection = 'asc' | 'desc';
23
+
24
+ export type CompareMode = 'none' | 'lastMonth' | 'custom';
25
+
26
+ export interface UnifiedResource {
27
+ id: string;
28
+ name: string;
29
+ type: ResourceType;
30
+ project: string;
31
+ /** GitHub repository URL for the project (if available) */
32
+ repoUrl?: string | null;
33
+ usage: {
34
+ value: number;
35
+ unit: string;
36
+ formatted: string;
37
+ };
38
+ /** Percentage of monthly limit used (0-100+) */
39
+ limitPct?: number | null;
40
+ costCurrent: number;
41
+ costPrior: number;
42
+ costDelta: number;
43
+ costDeltaPct: number | 'NEW' | null;
44
+ status: ResourceStatus;
45
+ }
46
+
47
+ export interface SparklinePoint {
48
+ date: string;
49
+ value: number;
50
+ }
51
+
52
+ export interface DateRange {
53
+ startDate: string;
54
+ endDate: string;
55
+ }
56
+
57
+ export interface FilterState {
58
+ period: '24h' | '7d' | '30d';
59
+ project: 'all' | 'brand-copilot' | 'scout' | 'platform';
60
+ compareMode: CompareMode;
61
+ customDateRange?: DateRange;
62
+ serviceTypes: ResourceType[];
63
+ searchQuery: string;
64
+ onlyChanged: boolean;
65
+ nonZeroCost: boolean;
66
+ }
67
+
68
+ export interface SortState {
69
+ column: string;
70
+ direction: SortDirection;
71
+ }
72
+
73
+ // Resource type metadata
74
+ export const RESOURCE_TYPE_ICONS: Record<ResourceType, string> = {
75
+ worker: '⚡',
76
+ d1: '🗄️',
77
+ kv: '🔑',
78
+ r2: '📦',
79
+ vectorize: '🧮',
80
+ pages: '📄',
81
+ queues: '📬',
82
+ workflows: '🔄',
83
+ do: '🔗',
84
+ 'ai-gateway': '🤖',
85
+ };
86
+
87
+ export const RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {
88
+ worker: 'Worker',
89
+ d1: 'D1',
90
+ kv: 'KV',
91
+ r2: 'R2',
92
+ vectorize: 'Vectorize',
93
+ pages: 'Pages',
94
+ queues: 'Queues',
95
+ workflows: 'Workflows',
96
+ do: 'Durable Objects',
97
+ 'ai-gateway': 'AI Gateway',
98
+ };
99
+
100
+ export const STATUS_COLOURS: Record<ResourceStatus, string> = {
101
+ healthy: '#10B981',
102
+ warning: '#F59E0B',
103
+ high: '#F97316',
104
+ critical: '#EF4444',
105
+ };
106
+
107
+ // Utility functions
108
+ export function formatCurrency(amount: number): string {
109
+ if (amount === 0) return '$0.00';
110
+ if (amount < 0.01) return '< $0.01';
111
+ return `$${amount.toFixed(2)}`;
112
+ }
113
+
114
+ export function formatDeltaPct(pct: number | 'NEW' | null): string {
115
+ if (pct === 'NEW') return 'NEW';
116
+ if (pct === null) return '—';
117
+ if (pct === 0) return '0%';
118
+ const sign = pct > 0 ? '+' : '';
119
+ return `${sign}${pct.toFixed(1)}%`;
120
+ }
121
+
122
+ export function getDeltaClass(pct: number | 'NEW' | null): string {
123
+ if (pct === 'NEW') return 'delta-new';
124
+ if (pct === null || pct === 0) return 'delta-neutral';
125
+ return pct > 0 ? 'delta-up' : 'delta-down';
126
+ }
127
+
128
+ export function formatLimitPct(pct: number | null | undefined): string {
129
+ if (pct === null || pct === undefined) return '—';
130
+ if (pct === 0) return '0%';
131
+ if (pct >= 100) return `${pct.toFixed(0)}%`;
132
+ if (pct >= 10) return `${pct.toFixed(0)}%`;
133
+ return `${pct.toFixed(1)}%`;
134
+ }
135
+
136
+ export function getLimitPctClass(pct: number | null | undefined): string {
137
+ if (pct === null || pct === undefined) return 'limit-neutral';
138
+ if (pct >= 90) return 'limit-critical';
139
+ if (pct >= 75) return 'limit-high';
140
+ if (pct >= 50) return 'limit-warning';
141
+ return 'limit-ok';
142
+ }
143
+
144
+ // ============================================================================
145
+ // Usage Overview Types (task-usage-redesign)
146
+ // ============================================================================
147
+
148
+ /**
149
+ * Provider type for usage metrics
150
+ */
151
+ export type UsageProvider = 'cloudflare' | 'github';
152
+
153
+ /**
154
+ * Utilization status based on percentage thresholds
155
+ */
156
+ export type UtilizationStatus = 'ok' | 'warning' | 'critical' | 'overage';
157
+
158
+ /**
159
+ * Normalized resource metric for cross-provider comparison
160
+ */
161
+ export interface ResourceMetric {
162
+ id: string;
163
+ label: string;
164
+ provider: UsageProvider;
165
+ current: number;
166
+ limit: number | null; // null if unlimited
167
+ unit: string;
168
+ percentage: number;
169
+ costEstimate: number;
170
+ status: UtilizationStatus;
171
+ overage: number; // Amount over limit (0 if under)
172
+ overageCost: number; // Cost of overage
173
+ }
174
+
175
+ /**
176
+ * Per-project usage breakdown with service-level detail
177
+ */
178
+ export interface ProjectServiceBreakdown {
179
+ projectId: string;
180
+ projectName: string;
181
+ services: ResourceMetric[];
182
+ totalCost: number;
183
+ totalUtilization: number; // Weighted average or max
184
+ circuitBreakerStatus: 'active' | 'inactive' | 'triggered';
185
+ }
186
+
187
+ /**
188
+ * Global health summary for a provider
189
+ */
190
+ export interface ProviderHealth {
191
+ provider: UsageProvider;
192
+ percentage: number;
193
+ warnings: number; // Count of services in warning/critical status
194
+ status: UtilizationStatus;
195
+ }
196
+
197
+ /**
198
+ * GitHub repository usage breakdown
199
+ */
200
+ export interface RepoUsage {
201
+ repoName: string;
202
+ actionsMinutes: number;
203
+ actionsMinutesPct: number;
204
+ storageBytes: number;
205
+ }
206
+
207
+ /**
208
+ * Complete dashboard data response
209
+ */
210
+ export interface UsageOverviewData {
211
+ timestamp: string;
212
+ period: {
213
+ start: string;
214
+ end: string;
215
+ };
216
+ health: {
217
+ cloudflare: ProviderHealth;
218
+ github: ProviderHealth;
219
+ };
220
+ burnRate: {
221
+ mtdCost: number;
222
+ projectedMonthlyCost: number;
223
+ dailyBurnRate: number;
224
+ vsLastMonthPct: number;
225
+ budget: number | null;
226
+ };
227
+ cloudflareServices: ResourceMetric[];
228
+ githubServices: ResourceMetric[];
229
+ projectBreakdowns: ProjectServiceBreakdown[];
230
+ githubRepos: RepoUsage[];
231
+ }
232
+
233
+ /**
234
+ * Calculate utilization status from percentage
235
+ */
236
+ export function getUtilizationStatus(percentage: number): UtilizationStatus {
237
+ if (percentage >= 100) return 'overage';
238
+ if (percentage >= 90) return 'critical';
239
+ if (percentage >= 70) return 'warning';
240
+ return 'ok';
241
+ }
242
+
243
+ /**
244
+ * Status colours for utilization bars
245
+ */
246
+ export const UTILIZATION_COLOURS: Record<UtilizationStatus, string> = {
247
+ ok: '#22c55e', // green-500
248
+ warning: '#eab308', // yellow-500
249
+ critical: '#ef4444', // red-500
250
+ overage: '#dc2626', // red-600
251
+ };
252
+
253
+ /**
254
+ * Format large numbers with K/M/B suffixes
255
+ */
256
+ export function formatLargeNumber(num: number): string {
257
+ if (num >= 1_000_000_000) {
258
+ return `${(num / 1_000_000_000).toFixed(1)}B`;
259
+ }
260
+ if (num >= 1_000_000) {
261
+ return `${(num / 1_000_000).toFixed(1)}M`;
262
+ }
263
+ if (num >= 1_000) {
264
+ return `${(num / 1_000).toFixed(1)}K`;
265
+ }
266
+ return num.toFixed(0);
267
+ }
268
+
269
+ /**
270
+ * Format bytes to human readable (GB, MB, etc.)
271
+ */
272
+ export function formatBytes(bytes: number): string {
273
+ if (bytes >= 1_000_000_000) {
274
+ return `${(bytes / 1_000_000_000).toFixed(2)} GB`;
275
+ }
276
+ if (bytes >= 1_000_000) {
277
+ return `${(bytes / 1_000_000).toFixed(2)} MB`;
278
+ }
279
+ if (bytes >= 1_000) {
280
+ return `${(bytes / 1_000).toFixed(2)} KB`;
281
+ }
282
+ return `${bytes} B`;
283
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * AlertBanner Component
3
+ *
4
+ * Displays critical alerts for services approaching or exceeding limits.
5
+ * Dismissible alerts are stored in localStorage.
6
+ * Supports collapse/expand for managing alert visibility.
7
+ */
8
+
9
+ import { useState, useMemo } from 'react';
10
+ import { AlertTriangle, X, ChevronDown, ChevronUp, ChevronsUpDown } from 'lucide-react';
11
+ import { clsx } from 'clsx';
12
+ import type { ServiceUtilisation, BurnRateData } from './types';
13
+
14
+ interface AlertBannerProps {
15
+ services: ServiceUtilisation[];
16
+ burnRate: BurnRateData | null;
17
+ onDismiss: (alertId: string) => void;
18
+ dismissedAlerts: Set<string>;
19
+ }
20
+
21
+ interface AlertItem {
22
+ id: string;
23
+ type: 'budget' | 'service';
24
+ title: string;
25
+ detail: string;
26
+ }
27
+
28
+ export function AlertBanner({ services, burnRate, onDismiss, dismissedAlerts }: AlertBannerProps) {
29
+ // Collapsed state - which alerts are collapsed (show title only)
30
+ const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
31
+ // All alerts collapsed state for the header toggle
32
+ const [allCollapsed, setAllCollapsed] = useState(false);
33
+
34
+ // Filter to critical/overage alerts that haven't been dismissed
35
+ const criticalAlerts = services
36
+ .filter((s) => s.status === 'critical' || s.status === 'overage')
37
+ .filter((s) => !dismissedAlerts.has(s.id));
38
+
39
+ // Check for budget alert
40
+ const showBudgetAlert = burnRate?.status === 'red' && !dismissedAlerts.has('budget');
41
+
42
+ // Build unified alert list for count
43
+ const alerts = useMemo(() => {
44
+ const list: AlertItem[] = [];
45
+
46
+ if (showBudgetAlert && burnRate) {
47
+ list.push({
48
+ id: 'budget',
49
+ type: 'budget',
50
+ title: 'BUDGET ALERT: Projected spend exceeds target',
51
+ detail: `Projected: $${burnRate.projectedMonthlyCost.toFixed(2)} | Daily burn: $${burnRate.dailyBurnRate.toFixed(2)}/day | ${burnRate.daysRemaining} days remaining`,
52
+ });
53
+ }
54
+
55
+ for (const alert of criticalAlerts) {
56
+ list.push({
57
+ id: alert.id,
58
+ type: 'service',
59
+ title: `CRITICAL: ${alert.label} at ${alert.percentage.toFixed(0)}% of limit`,
60
+ detail: `Current: ${alert.current.toLocaleString()} ${alert.unit} / Limit: ${alert.limit.toLocaleString()} ${alert.unit}${alert.costEstimate > 0 ? ` | Est. cost: $${alert.costEstimate.toFixed(2)}` : ''}`,
61
+ });
62
+ }
63
+
64
+ return list;
65
+ }, [showBudgetAlert, burnRate, criticalAlerts]);
66
+
67
+ if (alerts.length === 0) {
68
+ return null;
69
+ }
70
+
71
+ const toggleCollapse = (id: string) => {
72
+ setCollapsed((prev) => {
73
+ const next = new Set(prev);
74
+ if (next.has(id)) {
75
+ next.delete(id);
76
+ } else {
77
+ next.add(id);
78
+ }
79
+ return next;
80
+ });
81
+ };
82
+
83
+ const toggleAll = () => {
84
+ if (allCollapsed) {
85
+ // Expand all
86
+ setCollapsed(new Set());
87
+ setAllCollapsed(false);
88
+ } else {
89
+ // Collapse all
90
+ setCollapsed(new Set(alerts.map((a) => a.id)));
91
+ setAllCollapsed(true);
92
+ }
93
+ };
94
+
95
+ return (
96
+ <div className="mb-6">
97
+ {/* Alert Header with count and collapse toggle */}
98
+ <div className="flex items-center justify-between mb-2">
99
+ <div className="flex items-center gap-2">
100
+ <AlertTriangle className="w-4 h-4 text-rose-400" />
101
+ <span className="text-xs font-semibold text-rose-300 uppercase tracking-wider">
102
+ {alerts.length} Active Alert{alerts.length !== 1 ? 's' : ''}
103
+ </span>
104
+ </div>
105
+ <button
106
+ type="button"
107
+ onClick={toggleAll}
108
+ className="flex items-center gap-1 text-xs text-gray-600 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-300 transition-colors px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-slate-800"
109
+ >
110
+ <ChevronsUpDown className="w-3 h-3" />
111
+ {allCollapsed ? 'Expand All' : 'Collapse All'}
112
+ </button>
113
+ </div>
114
+
115
+ {/* Alerts List */}
116
+ <div className="space-y-2">
117
+ {alerts.map((alert) => {
118
+ const isCollapsed = collapsed.has(alert.id);
119
+
120
+ return (
121
+ <div
122
+ key={alert.id}
123
+ className="bg-rose-500/10 border border-rose-500/30 rounded-sm overflow-hidden transition-all"
124
+ >
125
+ {/* Alert Header Row - always visible */}
126
+ <div className="p-3 flex items-center gap-3">
127
+ <button
128
+ type="button"
129
+ onClick={() => toggleCollapse(alert.id)}
130
+ className="text-rose-400 hover:text-rose-300 p-1 rounded hover:bg-rose-500/20 transition-colors"
131
+ aria-label={isCollapsed ? 'Expand alert' : 'Collapse alert'}
132
+ >
133
+ {isCollapsed ? (
134
+ <ChevronDown className="w-4 h-4" />
135
+ ) : (
136
+ <ChevronUp className="w-4 h-4" />
137
+ )}
138
+ </button>
139
+ <AlertTriangle className="w-4 h-4 text-rose-400 flex-shrink-0" />
140
+ <span className="text-rose-200 font-semibold text-sm flex-1 truncate">
141
+ {alert.title}
142
+ </span>
143
+ <button
144
+ type="button"
145
+ onClick={() => onDismiss(alert.id)}
146
+ className="text-rose-400 hover:text-rose-300 p-1 rounded hover:bg-rose-500/20 transition-colors"
147
+ aria-label="Dismiss alert"
148
+ >
149
+ <X className="w-4 h-4" />
150
+ </button>
151
+ </div>
152
+
153
+ {/* Alert Details - collapsible */}
154
+ <div
155
+ className={clsx(
156
+ 'overflow-hidden transition-all duration-200 ease-in-out',
157
+ isCollapsed ? 'max-h-0' : 'max-h-24'
158
+ )}
159
+ >
160
+ <div className="px-4 pb-3 pl-12">
161
+ <p className="text-rose-300/80 text-xs font-mono">{alert.detail}</p>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ );
166
+ })}
167
+ </div>
168
+ </div>
169
+ );
170
+ }
171
+
172
+ export default AlertBanner;