@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,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);