@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,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Dashboard Actions
|
|
3
|
+
*
|
|
4
|
+
* Functions for interacting with the usage store.
|
|
5
|
+
* Handles API fetching, URL sync, and Melbourne timezone formatting.
|
|
6
|
+
* Part of task-19: Usage Dashboard Refactor Phase 1 - Foundation
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { syncFromURL, initSubscriptions, fetchDailyData } from './usageActions';
|
|
11
|
+
*
|
|
12
|
+
* // On page load
|
|
13
|
+
* syncFromURL();
|
|
14
|
+
* initSubscriptions();
|
|
15
|
+
*
|
|
16
|
+
* // Manually fetch data
|
|
17
|
+
* await fetchDailyData();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
$period,
|
|
23
|
+
$project,
|
|
24
|
+
$customDateRange,
|
|
25
|
+
$dailyData,
|
|
26
|
+
$isLoading,
|
|
27
|
+
$hasLegacyData,
|
|
28
|
+
$selectedDate,
|
|
29
|
+
$projects,
|
|
30
|
+
$allowances,
|
|
31
|
+
$projectedCost,
|
|
32
|
+
$projectsLoading,
|
|
33
|
+
$githubUsage,
|
|
34
|
+
$error,
|
|
35
|
+
type Period,
|
|
36
|
+
type ProjectData,
|
|
37
|
+
type AllowancesData,
|
|
38
|
+
type ProjectedCost,
|
|
39
|
+
type GitHubUsageData,
|
|
40
|
+
} from './usageStore';
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Melbourne Timezone Formatters
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format a date for Melbourne timezone (DD/MM/YYYY)
|
|
48
|
+
*/
|
|
49
|
+
export function formatDateMelbourne(date: Date): string {
|
|
50
|
+
return date.toLocaleDateString('en-AU', {
|
|
51
|
+
timeZone: 'Australia/Melbourne',
|
|
52
|
+
year: 'numeric',
|
|
53
|
+
month: '2-digit',
|
|
54
|
+
day: '2-digit',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Format a date-time for Melbourne timezone
|
|
60
|
+
*/
|
|
61
|
+
export function formatDateTimeMelbourne(date: Date): string {
|
|
62
|
+
return date.toLocaleString('en-AU', {
|
|
63
|
+
timeZone: 'Australia/Melbourne',
|
|
64
|
+
dateStyle: 'medium',
|
|
65
|
+
timeStyle: 'short',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format a date for display (e.g., "7 Jan")
|
|
71
|
+
*/
|
|
72
|
+
export function formatDateShort(date: Date): string {
|
|
73
|
+
return date.toLocaleDateString('en-AU', {
|
|
74
|
+
timeZone: 'Australia/Melbourne',
|
|
75
|
+
day: 'numeric',
|
|
76
|
+
month: 'short',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get current Melbourne date as YYYY-MM-DD
|
|
82
|
+
*/
|
|
83
|
+
export function getMelbourneDateString(): string {
|
|
84
|
+
const now = new Date();
|
|
85
|
+
return now.toLocaleDateString('en-CA', {
|
|
86
|
+
timeZone: 'Australia/Melbourne',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// URL Sync
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Sync store state FROM URL (on page load)
|
|
96
|
+
*/
|
|
97
|
+
export function syncFromURL(): void {
|
|
98
|
+
if (typeof window === 'undefined') return;
|
|
99
|
+
|
|
100
|
+
const params = new URLSearchParams(window.location.search);
|
|
101
|
+
|
|
102
|
+
// Period
|
|
103
|
+
const period = params.get('period') as Period | null;
|
|
104
|
+
if (period && ['24h', '7d', '30d', 'custom'].includes(period)) {
|
|
105
|
+
$period.set(period);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Project
|
|
109
|
+
const project = params.get('project');
|
|
110
|
+
if (project) {
|
|
111
|
+
$project.set(project);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Custom date range
|
|
115
|
+
const start = params.get('startDate');
|
|
116
|
+
const end = params.get('endDate');
|
|
117
|
+
if (start && end) {
|
|
118
|
+
$customDateRange.set({ start, end });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Selected date
|
|
122
|
+
const selectedDate = params.get('selectedDate');
|
|
123
|
+
if (selectedDate) {
|
|
124
|
+
$selectedDate.set(selectedDate);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sync store state TO URL (on state change)
|
|
130
|
+
*/
|
|
131
|
+
export function syncToURL(): void {
|
|
132
|
+
if (typeof window === 'undefined') return;
|
|
133
|
+
|
|
134
|
+
const url = new URL(window.location.href);
|
|
135
|
+
|
|
136
|
+
// Period
|
|
137
|
+
url.searchParams.set('period', $period.get());
|
|
138
|
+
|
|
139
|
+
// Project
|
|
140
|
+
const project = $project.get();
|
|
141
|
+
if (project && project !== 'all') {
|
|
142
|
+
url.searchParams.set('project', project);
|
|
143
|
+
} else {
|
|
144
|
+
url.searchParams.delete('project');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Custom date range
|
|
148
|
+
const { start, end } = $customDateRange.get();
|
|
149
|
+
if ($period.get() === 'custom' && start && end) {
|
|
150
|
+
url.searchParams.set('startDate', start);
|
|
151
|
+
url.searchParams.set('endDate', end);
|
|
152
|
+
} else {
|
|
153
|
+
url.searchParams.delete('startDate');
|
|
154
|
+
url.searchParams.delete('endDate');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Selected date
|
|
158
|
+
const selected = $selectedDate.get();
|
|
159
|
+
if (selected) {
|
|
160
|
+
url.searchParams.set('selectedDate', selected);
|
|
161
|
+
} else {
|
|
162
|
+
url.searchParams.delete('selectedDate');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
window.history.replaceState({}, '', url.toString());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Data Fetching
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Fetch daily data from API
|
|
174
|
+
*
|
|
175
|
+
* Uses /api/usage/daily endpoint which returns DailyCostData with:
|
|
176
|
+
* - days: Array of DailyCostBreakdown (date, services, total)
|
|
177
|
+
* - summary: { totalCost, averageDailyCost, highestDay, lowestDay }
|
|
178
|
+
*/
|
|
179
|
+
export async function fetchDailyData(options?: { nocache?: boolean }): Promise<void> {
|
|
180
|
+
$isLoading.set(true);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const params = new URLSearchParams({
|
|
184
|
+
period: $period.get(),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const { start, end } = $customDateRange.get();
|
|
188
|
+
if ($period.get() === 'custom' && start && end) {
|
|
189
|
+
params.set('startDate', start);
|
|
190
|
+
params.set('endDate', end);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add nocache parameter if requested (bypasses KV cache)
|
|
194
|
+
if (options?.nocache) {
|
|
195
|
+
params.set('nocache', 'true');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fetch from /api/usage/daily endpoint (not /api/usage)
|
|
199
|
+
// Include credentials to pass Cloudflare Access JWT cookie
|
|
200
|
+
const response = await fetch(`/api/usage/daily?${params}`, {
|
|
201
|
+
credentials: 'include',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`API error: ${response.status}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const json = await response.json();
|
|
209
|
+
|
|
210
|
+
// The /api/usage/daily endpoint returns { success, period, data: DailyCostData, cached, timestamp }
|
|
211
|
+
// DailyCostData has { days: DailyCostBreakdown[], summary: {...}, hasLegacyData?: boolean }
|
|
212
|
+
if (json.success && json.data?.days) {
|
|
213
|
+
$dailyData.set(json.data.days);
|
|
214
|
+
// Set legacy data flag - true if any day has rollupVersion=1 (inflated SUM aggregation)
|
|
215
|
+
$hasLegacyData.set(json.data.hasLegacyData === true);
|
|
216
|
+
} else {
|
|
217
|
+
console.warn('Daily data response missing expected structure:', json);
|
|
218
|
+
$dailyData.set([]);
|
|
219
|
+
$hasLegacyData.set(false);
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('Failed to fetch daily data:', error);
|
|
223
|
+
$dailyData.set([]);
|
|
224
|
+
$hasLegacyData.set(false);
|
|
225
|
+
} finally {
|
|
226
|
+
$isLoading.set(false);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Fetch projects, allowances, and projected cost from API
|
|
232
|
+
*
|
|
233
|
+
* Uses /api/usage/projects endpoint which returns:
|
|
234
|
+
* - projects: Array of ProjectData with resourceCount
|
|
235
|
+
* - allowances: Service allowances object
|
|
236
|
+
* - projectedCost: MTD cost and projection
|
|
237
|
+
*/
|
|
238
|
+
export async function fetchProjects(): Promise<void> {
|
|
239
|
+
$projectsLoading.set(true);
|
|
240
|
+
$error.set(null);
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const response = await fetch('/api/usage/projects', {
|
|
244
|
+
credentials: 'include',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
throw new Error(`API error: ${response.status}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const json = await response.json();
|
|
252
|
+
|
|
253
|
+
if (json.success) {
|
|
254
|
+
// Update projects store
|
|
255
|
+
if (json.projects) {
|
|
256
|
+
$projects.set(json.projects as ProjectData[]);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Update allowances store
|
|
260
|
+
if (json.allowances) {
|
|
261
|
+
$allowances.set(json.allowances as AllowancesData);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Update projected cost store
|
|
265
|
+
if (json.projectedCost) {
|
|
266
|
+
$projectedCost.set(json.projectedCost as ProjectedCost);
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
console.warn('Projects response missing success:', json);
|
|
270
|
+
$error.set(json.error || 'Failed to fetch projects');
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('Failed to fetch projects:', error);
|
|
274
|
+
$error.set(error instanceof Error ? error.message : 'Failed to fetch projects');
|
|
275
|
+
} finally {
|
|
276
|
+
$projectsLoading.set(false);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Fetch GitHub usage data from utilisation endpoint
|
|
282
|
+
*
|
|
283
|
+
* Uses /api/usage/utilization endpoint which returns:
|
|
284
|
+
* - github: MTD usage and plan details
|
|
285
|
+
*/
|
|
286
|
+
export async function fetchGitHubUsage(): Promise<void> {
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetch('/api/usage/utilization', {
|
|
289
|
+
credentials: 'include',
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (!response.ok) {
|
|
293
|
+
console.warn('GitHub usage fetch failed:', response.status);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const json = await response.json();
|
|
298
|
+
|
|
299
|
+
if (json.success && json.github) {
|
|
300
|
+
$githubUsage.set(json.github as GitHubUsageData);
|
|
301
|
+
}
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error('Failed to fetch GitHub usage:', error);
|
|
304
|
+
// Don't set error state - GitHub data is supplementary
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Fetch all usage data in parallel
|
|
310
|
+
*
|
|
311
|
+
* Consolidates multiple API calls for initial page load:
|
|
312
|
+
* - fetchDailyData(): Daily cost breakdown
|
|
313
|
+
* - fetchProjects(): Projects, allowances, projected cost
|
|
314
|
+
* - fetchGitHubUsage(): GitHub MTD usage
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* // On page load
|
|
319
|
+
* await fetchAll();
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export async function fetchAll(): Promise<void> {
|
|
323
|
+
$isLoading.set(true);
|
|
324
|
+
$projectsLoading.set(true);
|
|
325
|
+
$error.set(null);
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
// Fetch all data in parallel
|
|
329
|
+
await Promise.all([fetchDailyData(), fetchProjects(), fetchGitHubUsage()]);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error('Failed to fetch all usage data:', error);
|
|
332
|
+
$error.set(error instanceof Error ? error.message : 'Failed to fetch usage data');
|
|
333
|
+
}
|
|
334
|
+
// Loading states are managed by individual fetch functions
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// Subscriptions
|
|
339
|
+
// ============================================================================
|
|
340
|
+
|
|
341
|
+
let unsubscribers: (() => void)[] = [];
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Initialise store subscriptions for auto-fetching and URL sync.
|
|
345
|
+
* Call this once on page load.
|
|
346
|
+
*/
|
|
347
|
+
export function initSubscriptions(): void {
|
|
348
|
+
// Prevent double initialisation
|
|
349
|
+
if (unsubscribers.length > 0) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Auto-fetch when period changes
|
|
354
|
+
unsubscribers.push(
|
|
355
|
+
$period.subscribe(() => {
|
|
356
|
+
fetchDailyData();
|
|
357
|
+
syncToURL();
|
|
358
|
+
})
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
// Auto-fetch when project changes
|
|
362
|
+
unsubscribers.push(
|
|
363
|
+
$project.subscribe(() => {
|
|
364
|
+
fetchDailyData();
|
|
365
|
+
syncToURL();
|
|
366
|
+
})
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Auto-fetch when custom date range changes (only if period is 'custom')
|
|
370
|
+
unsubscribers.push(
|
|
371
|
+
$customDateRange.subscribe(() => {
|
|
372
|
+
if ($period.get() === 'custom') {
|
|
373
|
+
fetchDailyData();
|
|
374
|
+
syncToURL();
|
|
375
|
+
}
|
|
376
|
+
})
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Sync selected date to URL
|
|
380
|
+
unsubscribers.push($selectedDate.subscribe(() => syncToURL()));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Clean up subscriptions.
|
|
385
|
+
* Call this on page unload if needed.
|
|
386
|
+
*/
|
|
387
|
+
export function destroySubscriptions(): void {
|
|
388
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
389
|
+
unsubscribers = [];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ============================================================================
|
|
393
|
+
// Actions (for UI components)
|
|
394
|
+
// ============================================================================
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Set the period filter
|
|
398
|
+
*/
|
|
399
|
+
export function setPeriod(period: Period): void {
|
|
400
|
+
$period.set(period);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Set the project filter
|
|
405
|
+
*/
|
|
406
|
+
export function setProject(project: string): void {
|
|
407
|
+
$project.set(project);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Set custom date range
|
|
412
|
+
*/
|
|
413
|
+
export function setCustomDateRange(start: string, end: string): void {
|
|
414
|
+
$customDateRange.set({ start, end });
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Select a date (from chart click)
|
|
419
|
+
*/
|
|
420
|
+
export function selectDate(date: string | null): void {
|
|
421
|
+
$selectedDate.set(date);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Clear the selected date
|
|
426
|
+
*/
|
|
427
|
+
export function clearSelection(): void {
|
|
428
|
+
$selectedDate.set(null);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Reset all filters to defaults
|
|
433
|
+
*/
|
|
434
|
+
export function resetFilters(): void {
|
|
435
|
+
$period.set('30d');
|
|
436
|
+
$project.set('all');
|
|
437
|
+
$customDateRange.set({ start: '', end: '' });
|
|
438
|
+
$selectedDate.set(null);
|
|
439
|
+
}
|