@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.
- package/README.md +2 -5
- package/dist/check-upgrade.d.ts +29 -0
- package/dist/check-upgrade.js +97 -0
- package/dist/index.js +59 -4
- package/dist/manifest.d.ts +2 -0
- package/dist/scaffold.js +5 -1
- package/dist/templates.d.ts +6 -1
- package/dist/templates.js +141 -3
- package/dist/upgrade.d.ts +1 -0
- package/dist/upgrade.js +21 -2
- package/package.json +1 -1
- package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
- package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
- package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
- package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
- package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
- package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
- package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
- package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
- package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
- package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
- package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
- package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
- package/templates/full/dashboard/src/pages/feedback.astro +365 -0
- package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
- package/templates/full/dashboard/src/pages/map.astro +561 -0
- package/templates/full/dashboard/src/pages/revenue.astro +72 -0
- package/templates/full/dashboard/src/pages/tests.astro +431 -0
- package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
- package/templates/full/scripts/ops/verify-account-total.ts +256 -0
- package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
- package/templates/full/tests/integration/r2-archive.test.ts +108 -0
- package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
- package/templates/shared/.github/workflows/validate-controls.yml +27 -0
- package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
- package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
- package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
- package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
- package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
- package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
- package/templates/shared/dashboard/src/components/Toast.astro +170 -0
- package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
- package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
- package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
- package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
- package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
- package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
- package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
- package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
- package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
- package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
- package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
- package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
- package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
- package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
- package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
- package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
- package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
- package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
- package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
- package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
- package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
- package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
- package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
- package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
- package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
- package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
- package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
- package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
- package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
- package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
- package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
- package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
- package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
- package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
- package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
- package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
- package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
- package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
- package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
- package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
- package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
- package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
- package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
- package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
- package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
- package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
- package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
- package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
- package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
- package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
- package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
- package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
- package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
- package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
- package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
- package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
- package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
- package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
- package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
- package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
- package/templates/standard/tests/integration/connectors.test.ts +241 -0
- package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
- 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
|
+
}
|