@littlebearapps/platform-admin-sdk 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +2 -5
  2. package/dist/check-upgrade.d.ts +29 -0
  3. package/dist/check-upgrade.js +97 -0
  4. package/dist/index.js +59 -4
  5. package/dist/manifest.d.ts +2 -0
  6. package/dist/scaffold.js +5 -1
  7. package/dist/templates.d.ts +6 -1
  8. package/dist/templates.js +141 -3
  9. package/dist/upgrade.d.ts +1 -0
  10. package/dist/upgrade.js +21 -2
  11. package/package.json +1 -1
  12. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  13. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  14. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  15. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  16. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  17. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  18. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  19. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  20. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  21. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  22. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  23. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  24. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  25. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  26. package/templates/full/dashboard/src/pages/map.astro +561 -0
  27. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  28. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  29. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  30. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  31. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  32. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  33. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  34. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  35. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  36. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  37. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  38. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  39. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  40. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  41. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  42. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  43. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  44. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  45. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  46. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  47. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  48. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  49. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  50. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  51. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  52. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  53. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  54. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  55. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  56. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  57. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  58. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  59. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  60. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  61. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  62. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  63. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  64. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  65. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  66. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  67. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  68. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  69. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  70. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  71. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  72. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  73. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  74. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  75. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  76. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  77. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  78. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  79. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  80. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  81. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  82. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  83. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  84. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  85. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  86. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  87. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  88. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  89. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  90. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  91. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  92. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  93. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  94. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  95. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  96. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  97. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  98. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  99. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  100. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  101. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  102. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  103. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  104. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  105. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  106. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  107. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  108. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  109. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  110. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  111. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  112. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  113. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  114. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  115. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  116. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  117. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  118. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  119. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  120. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  121. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  122. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Usage Dashboard State Store
3
+ *
4
+ * Centralised nanostores for reactive state management.
5
+ * Part of task-19: Usage Dashboard Refactor Phase 1 - Foundation
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { $period, $dailyData, $totalCost } from './usageStore';
10
+ *
11
+ * // Read current value
12
+ * console.log($period.get()); // '30d'
13
+ *
14
+ * // Subscribe to changes
15
+ * $dailyData.subscribe(data => console.log('Data updated:', data.length));
16
+ *
17
+ * // Set value
18
+ * $period.set('7d');
19
+ * ```
20
+ */
21
+
22
+ import { atom, map, computed } from 'nanostores';
23
+ import type { DailyCostBreakdown } from '../../../lib/cloudflare/graphql';
24
+
25
+ // ============================================================================
26
+ // Period Selection
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Available time periods for filtering
31
+ */
32
+ export type Period = '24h' | '7d' | '30d' | 'custom';
33
+
34
+ /**
35
+ * Current period selection
36
+ * @default '30d'
37
+ */
38
+ export const $period = atom<Period>('30d');
39
+
40
+ // ============================================================================
41
+ // Project Filter
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Current project filter
46
+ * @default 'all'
47
+ */
48
+ export const $project = atom<string>('all');
49
+
50
+ // ============================================================================
51
+ // Custom Date Range
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Custom date range (used when period === 'custom')
56
+ */
57
+ export const $customDateRange = map<{ start: string; end: string }>({
58
+ start: '',
59
+ end: '',
60
+ });
61
+
62
+ // ============================================================================
63
+ // Timezone
64
+ // ============================================================================
65
+
66
+ /**
67
+ * Timezone for date formatting
68
+ * @default 'Australia/Melbourne'
69
+ */
70
+ export const $timezone = atom<string>('Australia/Melbourne');
71
+
72
+ // ============================================================================
73
+ // Daily Cost Data
74
+ // ============================================================================
75
+
76
+ /**
77
+ * Daily cost breakdown data from API
78
+ */
79
+ export const $dailyData = atom<DailyCostBreakdown[]>([]);
80
+
81
+ // ============================================================================
82
+ // Loading State
83
+ // ============================================================================
84
+
85
+ /**
86
+ * Whether data is currently being fetched
87
+ */
88
+ export const $isLoading = atom<boolean>(false);
89
+
90
+ /**
91
+ * Whether any day in the current data has legacy/inflated values (rollupVersion=1)
92
+ * Legacy data was aggregated using SUM() on cumulative hourly values, resulting in ~15x inflated costs.
93
+ * Accurate data (rollupVersion=2) uses MAX() aggregation.
94
+ */
95
+ export const $hasLegacyData = atom<boolean>(false);
96
+
97
+ // ============================================================================
98
+ // Selection State
99
+ // ============================================================================
100
+
101
+ /**
102
+ * Currently selected date (from chart click)
103
+ * @default null
104
+ */
105
+ export const $selectedDate = atom<string | null>(null);
106
+
107
+ // ============================================================================
108
+ // Computed Values
109
+ // ============================================================================
110
+
111
+ /**
112
+ * Total cost across all days
113
+ */
114
+ export const $totalCost = computed($dailyData, (data) => data.reduce((sum, d) => sum + d.total, 0));
115
+
116
+ /**
117
+ * Date range display string formatted for Melbourne timezone
118
+ */
119
+ export const $dateRangeDisplay = computed([$dailyData, $timezone], (data, tz) => {
120
+ if (data.length === 0) return '';
121
+
122
+ const first = data[0].date;
123
+ const last = data[data.length - 1].date;
124
+
125
+ const opts: Intl.DateTimeFormatOptions = {
126
+ timeZone: tz,
127
+ day: 'numeric',
128
+ month: 'short',
129
+ };
130
+
131
+ const startStr = new Date(first).toLocaleDateString('en-AU', opts);
132
+ const endStr = new Date(last).toLocaleDateString('en-AU', opts);
133
+
134
+ return `${startStr} - ${endStr}`;
135
+ });
136
+
137
+ /**
138
+ * Number of days in current data
139
+ */
140
+ export const $dayCount = computed($dailyData, (data) => data.length);
141
+
142
+ // ============================================================================
143
+ // GitHub Usage Data
144
+ // ============================================================================
145
+
146
+ /**
147
+ * GitHub usage data from the utilization endpoint
148
+ */
149
+ export interface GitHubUsageData {
150
+ mtdUsage: {
151
+ actionsMinutes: number;
152
+ actionsMinutesIncluded: number;
153
+ actionsMinutesUsagePct: number;
154
+ actionsStorageGbHours: number;
155
+ actionsStorageGbIncluded: number;
156
+ ghecUserMonths: number;
157
+ ghasCodeSecuritySeats: number;
158
+ ghasSecretProtectionSeats: number;
159
+ totalCost: number;
160
+ };
161
+ plan: {
162
+ name: string;
163
+ filledSeats: number;
164
+ totalSeats: number;
165
+ };
166
+ lastUpdated: string | null;
167
+ isStale: boolean;
168
+ }
169
+
170
+ /**
171
+ * GitHub usage data store (separate from period-filtered Cloudflare data)
172
+ * This data is fetched once and doesn't change with period selection.
173
+ */
174
+ export const $githubUsage = atom<GitHubUsageData | null>(null);
175
+
176
+ /**
177
+ * Average daily cost
178
+ */
179
+ export const $averageDailyCost = computed($dailyData, (data) => {
180
+ if (data.length === 0) return 0;
181
+ const total = data.reduce((sum, d) => sum + d.total, 0);
182
+ return total / data.length;
183
+ });
184
+
185
+ /**
186
+ * Cost breakdown by resource type (totals)
187
+ */
188
+ export const $resourceTotals = computed($dailyData, (data) => {
189
+ const totals = {
190
+ workers: 0,
191
+ d1: 0,
192
+ kv: 0,
193
+ r2: 0,
194
+ vectorize: 0,
195
+ aiGateway: 0,
196
+ durableObjects: 0,
197
+ workersAI: 0,
198
+ queues: 0,
199
+ };
200
+
201
+ for (const day of data) {
202
+ totals.workers += day.workers;
203
+ totals.d1 += day.d1;
204
+ totals.kv += day.kv;
205
+ totals.r2 += day.r2;
206
+ totals.vectorize += day.vectorize;
207
+ totals.aiGateway += day.aiGateway;
208
+ totals.durableObjects += day.durableObjects;
209
+ totals.workersAI += day.workersAI;
210
+ totals.queues += day.queues;
211
+ }
212
+
213
+ return totals;
214
+ });
215
+
216
+ /**
217
+ * Top spender - service type with highest cost in the period
218
+ */
219
+ export interface TopSpenderData {
220
+ /** Service type name */
221
+ service: string;
222
+ /** Display label */
223
+ label: string;
224
+ /** Total cost for this service */
225
+ cost: number;
226
+ /** Percentage of total cost */
227
+ percentageOfTotal: number;
228
+ }
229
+
230
+ const SERVICE_LABELS: Record<string, string> = {
231
+ workers: 'Workers',
232
+ d1: 'D1 Database',
233
+ kv: 'KV Storage',
234
+ r2: 'R2 Storage',
235
+ vectorize: 'Vectorize',
236
+ aiGateway: 'AI Gateway',
237
+ durableObjects: 'Durable Objects',
238
+ workersAI: 'Workers AI',
239
+ queues: 'Queues',
240
+ };
241
+
242
+ export const $topSpender = computed($resourceTotals, (totals): TopSpenderData | null => {
243
+ const entries = Object.entries(totals).filter(([_, cost]) => cost > 0);
244
+ if (entries.length === 0) return null;
245
+
246
+ const totalCost = entries.reduce((sum, [_, cost]) => sum + cost, 0);
247
+ const [topService, topCost] = entries.reduce(
248
+ (max, entry) => (entry[1] > max[1] ? entry : max),
249
+ entries[0]
250
+ );
251
+
252
+ return {
253
+ service: topService,
254
+ label: SERVICE_LABELS[topService] || topService,
255
+ cost: topCost,
256
+ percentageOfTotal: totalCost > 0 ? (topCost / totalCost) * 100 : 0,
257
+ };
258
+ });
259
+
260
+ // ============================================================================
261
+ // Projects & Allowances (from /api/usage/projects)
262
+ // ============================================================================
263
+
264
+ /**
265
+ * Service allowance definition
266
+ */
267
+ export interface AllowanceInfo {
268
+ limit: number;
269
+ unit: string;
270
+ }
271
+
272
+ /**
273
+ * Allowances object from API
274
+ */
275
+ export interface AllowancesData {
276
+ workers: AllowanceInfo;
277
+ d1_writes: AllowanceInfo;
278
+ kv_writes: AllowanceInfo;
279
+ r2_storage: AllowanceInfo;
280
+ durableObjects: AllowanceInfo;
281
+ vectorize: AllowanceInfo;
282
+ github_actions_minutes: AllowanceInfo;
283
+ }
284
+
285
+ /**
286
+ * Projected cost calculation
287
+ */
288
+ export interface ProjectedCost {
289
+ /** Current month-to-date cost in USD */
290
+ currentCost: number;
291
+ /** Number of days elapsed this month */
292
+ daysPassed: number;
293
+ /** Total days in the current month */
294
+ daysInMonth: number;
295
+ /** Projected end-of-month cost based on current burn rate */
296
+ projectedMonthlyCost: number;
297
+ }
298
+
299
+ /**
300
+ * Project from D1 registry with resource count
301
+ */
302
+ export interface ProjectData {
303
+ projectId: string;
304
+ displayName: string;
305
+ description: string | null;
306
+ color: string | null;
307
+ icon: string | null;
308
+ owner: string | null;
309
+ repoPath: string | null;
310
+ status: 'active' | 'archived' | 'development';
311
+ primaryResource: string | null;
312
+ customLimit: number | null;
313
+ /** Full GitHub repository URL */
314
+ repoUrl: string | null;
315
+ /** GitHub repository identifier */
316
+ githubRepoId: string | null;
317
+ /** Number of CF resources mapped to this project */
318
+ resourceCount: number;
319
+ }
320
+
321
+ /**
322
+ * Projects list store (from /api/usage/projects)
323
+ */
324
+ export const $projects = atom<ProjectData[]>([]);
325
+
326
+ /**
327
+ * Service allowances store
328
+ */
329
+ export const $allowances = atom<AllowancesData | null>(null);
330
+
331
+ /**
332
+ * Projected cost store
333
+ */
334
+ export const $projectedCost = atom<ProjectedCost | null>(null);
335
+
336
+ /**
337
+ * Projects loading state
338
+ */
339
+ export const $projectsLoading = atom<boolean>(false);
340
+
341
+ // ============================================================================
342
+ // Filter State (for resource table)
343
+ // ============================================================================
344
+
345
+ /**
346
+ * Resource table filter state (search, type filter, toggles)
347
+ */
348
+ export interface ResourceFilterState {
349
+ /** Search query string */
350
+ search: string;
351
+ /** Resource type filter ('all' or specific type) */
352
+ typeFilter: string;
353
+ /** Show only resources with cost changes */
354
+ showOnlyChanged: boolean;
355
+ /** Show only resources with non-zero cost */
356
+ showNonZeroCost: boolean;
357
+ }
358
+
359
+ /**
360
+ * Resource filter state store
361
+ */
362
+ export const $resourceFilters = map<ResourceFilterState>({
363
+ search: '',
364
+ typeFilter: 'all',
365
+ showOnlyChanged: false,
366
+ showNonZeroCost: false,
367
+ });
368
+
369
+ // ============================================================================
370
+ // Error State
371
+ // ============================================================================
372
+
373
+ /**
374
+ * Error message store (null when no error)
375
+ */
376
+ export const $error = atom<string | null>(null);
@@ -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
+ }