@littlebearapps/platform-admin-sdk 2.1.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 (115) hide show
  1. package/README.md +2 -5
  2. package/dist/templates.d.ts +1 -1
  3. package/dist/templates.js +121 -3
  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/FeatureUsageReport.tsx +339 -0
  9. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  10. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  11. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  12. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  13. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  14. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  15. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  16. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  17. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  18. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  19. package/templates/full/dashboard/src/pages/map.astro +561 -0
  20. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  21. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  22. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  23. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  24. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  25. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  26. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  27. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  28. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  29. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  30. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  31. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  32. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  33. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  34. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  35. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  36. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  37. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  38. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  39. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  40. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  41. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  42. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  43. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  44. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  45. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  46. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  47. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  48. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  49. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  50. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  51. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  52. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  53. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  54. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  55. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  56. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  57. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  58. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  59. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  60. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  61. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  62. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  63. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  64. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  65. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  66. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  67. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  68. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  69. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  70. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  71. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  72. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  73. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  74. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  75. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  76. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  77. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  78. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  79. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  80. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  81. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  82. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  83. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  84. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  85. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  86. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  87. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  88. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  89. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  90. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  91. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  92. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  93. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  94. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  95. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  96. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  97. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  98. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  99. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  100. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  101. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  102. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  103. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  104. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  105. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  106. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  107. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  108. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  109. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  110. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  111. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  112. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  113. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  114. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  115. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
@@ -0,0 +1,1079 @@
1
+ ---
2
+ /**
3
+ * DailyOverview.astro
4
+ *
5
+ * Comprehensive usage dashboard container that consolidates:
6
+ * - Filter controls (period, project, custom dates)
7
+ * - Stats display (Total, Avg/day, resource count)
8
+ * - Daily cost chart (stacked bar, full width)
9
+ * - Daily breakdown table (full width, below chart)
10
+ * - Secondary filters (compare, search, toggles, export, settings)
11
+ *
12
+ * Part of Option B consolidation: Move header controls into Daily section
13
+ */
14
+ import type { DailyCostBreakdown } from '../../../lib/cloudflare/graphql';
15
+ import CostChart from './CostChart.astro';
16
+ import CostTable from './CostTable.astro';
17
+ import PeriodButtons from '../filters/PeriodButtons.astro';
18
+ import InlineDateRange from '../filters/InlineDateRange.astro';
19
+ import ProjectSelect from '../filters/ProjectSelect.astro';
20
+
21
+ interface Props {
22
+ initialData?: DailyCostBreakdown[];
23
+ initialPeriod?: string;
24
+ initialProject?: string;
25
+ initialStartDate?: string;
26
+ initialEndDate?: string;
27
+ }
28
+
29
+ const {
30
+ initialData = [],
31
+ initialPeriod = '30d',
32
+ initialProject = 'all',
33
+ initialStartDate = '',
34
+ initialEndDate = '',
35
+ } = Astro.props;
36
+
37
+ // Calculate initial totals for hero stats
38
+ const totalCost = initialData.reduce((sum, d) => sum + d.total, 0);
39
+ const avgDailyCost = initialData.length > 0 ? totalCost / initialData.length : 0;
40
+ ---
41
+
42
+ <section class="daily-overview" data-component="daily-overview">
43
+ <!-- Geometric accent line -->
44
+ <div class="accent-bar" aria-hidden="true">
45
+ <div class="accent-segment accent-segment--primary"></div>
46
+ <div class="accent-segment accent-segment--secondary"></div>
47
+ <div class="accent-segment accent-segment--tertiary"></div>
48
+ </div>
49
+
50
+ <!-- Legacy Data Warning Banner (hidden by default, shown when data has rollupVersion=1) -->
51
+ <div id="legacy-data-warning" class="legacy-data-warning" style="display: none;" role="alert">
52
+ <span class="warning-icon" aria-hidden="true">⚠️</span>
53
+ <div class="warning-content">
54
+ <strong>Historical Data Notice</strong>
55
+ <p>
56
+ Some dates in this period contain legacy cost data that may show inflated values. These
57
+ dates predate our improved data collection method (Jan 4, 2026). Costs shown may be ~15x
58
+ higher than actual for affected days.
59
+ </p>
60
+ </div>
61
+ </div>
62
+
63
+ <!-- Main Header: Title + Primary Filters -->
64
+ <header class="section-header">
65
+ <div class="header-row header-row--primary">
66
+ <div class="header-title">
67
+ <h2 class="section-title">Usage Dashboard</h2>
68
+ <span class="section-subtitle" id="date-range-display">
69
+ {initialData.length > 0 ? `${initialData.length} days` : 'No data'}
70
+ </span>
71
+ </div>
72
+
73
+ <!-- Primary Filters: Period + Project -->
74
+ <div class="primary-filters">
75
+ <PeriodButtons initialPeriod={initialPeriod} />
76
+ <InlineDateRange initialStart={initialStartDate} initialEnd={initialEndDate} maxDays={90} />
77
+ <ProjectSelect initialProject={initialProject} />
78
+ </div>
79
+ </div>
80
+
81
+ <!-- Stats Row: Total, Avg, Resource Count -->
82
+ <div class="header-row header-row--stats">
83
+ <div class="stat-pills">
84
+ <div class="stat-pill stat-pill--hero">
85
+ <span class="stat-label">Period Cost</span>
86
+ <span class="stat-value stat-value--large" id="total-cost-display">
87
+ ${totalCost.toFixed(2)}
88
+ </span>
89
+ </div>
90
+ <div class="stat-pill">
91
+ <span class="stat-label">Avg/day</span>
92
+ <span class="stat-value" id="avg-cost-display">
93
+ ${avgDailyCost.toFixed(2)}
94
+ </span>
95
+ </div>
96
+ <div class="stat-pill stat-pill--muted">
97
+ <span class="stat-label">Resources</span>
98
+ <span class="stat-value" id="resource-count-value">0</span>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Secondary Actions: Refresh, Export, Settings -->
103
+ <div class="header-actions">
104
+ <button
105
+ type="button"
106
+ id="refresh-data"
107
+ class="action-button action-button--primary"
108
+ aria-label="Refresh data (bypass cache)"
109
+ title="Refresh data (bypass cache)"
110
+ >
111
+ <svg
112
+ width="16"
113
+ height="16"
114
+ viewBox="0 0 16 16"
115
+ fill="none"
116
+ xmlns="http://www.w3.org/2000/svg"
117
+ class="refresh-icon"
118
+ >
119
+ <path
120
+ d="M14 8A6 6 0 1 1 8 2v2"
121
+ stroke="currentColor"
122
+ stroke-width="1.5"
123
+ stroke-linecap="round"
124
+ stroke-linejoin="round"></path>
125
+ <path
126
+ d="M5 2 8 4 8 1"
127
+ stroke="currentColor"
128
+ stroke-width="1.5"
129
+ stroke-linecap="round"
130
+ stroke-linejoin="round"></path>
131
+ </svg>
132
+ <span class="action-label">Refresh</span>
133
+ </button>
134
+ <button
135
+ type="button"
136
+ id="export-csv"
137
+ class="action-button"
138
+ aria-label="Export as CSV"
139
+ title="Export as CSV"
140
+ >
141
+ <svg
142
+ width="16"
143
+ height="16"
144
+ viewBox="0 0 16 16"
145
+ fill="none"
146
+ xmlns="http://www.w3.org/2000/svg"
147
+ >
148
+ <path
149
+ d="M14 10v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-3M4.5 6.5 8 10l3.5-3.5M8 10V2"
150
+ stroke="currentColor"
151
+ stroke-width="1.5"
152
+ stroke-linecap="round"
153
+ stroke-linejoin="round"></path>
154
+ </svg>
155
+ <span class="action-label">CSV</span>
156
+ </button>
157
+ <button
158
+ type="button"
159
+ id="export-json"
160
+ class="action-button"
161
+ aria-label="Export as JSON"
162
+ title="Export as JSON"
163
+ >
164
+ <svg
165
+ width="16"
166
+ height="16"
167
+ viewBox="0 0 16 16"
168
+ fill="none"
169
+ xmlns="http://www.w3.org/2000/svg"
170
+ >
171
+ <path
172
+ d="M14 10v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-3M4.5 6.5 8 10l3.5-3.5M8 10V2"
173
+ stroke="currentColor"
174
+ stroke-width="1.5"
175
+ stroke-linecap="round"
176
+ stroke-linejoin="round"></path>
177
+ </svg>
178
+ <span class="action-label">JSON</span>
179
+ </button>
180
+ <a href="/usage/settings" class="action-button" title="Alert Thresholds Settings">
181
+ <svg
182
+ width="16"
183
+ height="16"
184
+ viewBox="0 0 20 20"
185
+ fill="none"
186
+ xmlns="http://www.w3.org/2000/svg"
187
+ >
188
+ <path
189
+ d="M8.325 2.317a1.724 1.724 0 0 1 3.35 0 1.723 1.723 0 0 0 2.573 1.066c1.067-.648 2.388.673 1.74 1.74a1.723 1.723 0 0 0 1.065 2.572c1.756.426 1.756 2.924 0 3.35a1.723 1.723 0 0 0-1.066 2.573c.649 1.067-.673 2.388-1.74 1.74a1.723 1.723 0 0 0-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.723 1.723 0 0 0-2.573-1.066c-1.067.649-2.388-.673-1.74-1.74a1.723 1.723 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.723 1.723 0 0 0 1.066-2.573c-.649-1.067.673-2.388 1.74-1.74a1.723 1.723 0 0 0 2.572-1.065Z"
190
+ stroke="currentColor"
191
+ stroke-width="1.5"
192
+ stroke-linecap="round"
193
+ stroke-linejoin="round"></path>
194
+ <path
195
+ d="M10 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"
196
+ stroke="currentColor"
197
+ stroke-width="1.5"
198
+ stroke-linecap="round"
199
+ stroke-linejoin="round"></path>
200
+ </svg>
201
+ <span class="action-label">Settings</span>
202
+ </a>
203
+ </div>
204
+ </div>
205
+
206
+ <!-- Tertiary Row: Toggles + Auto-refresh -->
207
+ <div class="header-row header-row--tertiary">
208
+ <div class="filter-toggles">
209
+ <button
210
+ type="button"
211
+ class="toggle-pill"
212
+ id="toggle-only-changed"
213
+ aria-pressed="false"
214
+ title="Show only resources with significant changes (>5% cost change or NEW)"
215
+ >
216
+ <span class="toggle-icon">Δ</span>
217
+ <span class="toggle-label">Only Changed</span>
218
+ </button>
219
+ <button
220
+ type="button"
221
+ class="toggle-pill"
222
+ id="toggle-non-zero-cost"
223
+ aria-pressed="false"
224
+ title="Hide resources with zero cost"
225
+ >
226
+ <span class="toggle-icon">$</span>
227
+ <span class="toggle-label">Non-zero</span>
228
+ </button>
229
+ </div>
230
+
231
+ <div class="auto-refresh">
232
+ <label class="toggle-switch" title="Auto-refresh every 30 seconds">
233
+ <input type="checkbox" id="auto-refresh-toggle" />
234
+ <span class="toggle-slider"></span>
235
+ </label>
236
+ <span class="toggle-text" id="auto-refresh-label">Auto-refresh</span>
237
+ <span class="refresh-countdown" id="refresh-countdown" style="display: none;"></span>
238
+ </div>
239
+
240
+ <!-- Loading indicator -->
241
+ <div class="loading-indicator" id="filter-loading" style="display: none;">
242
+ <svg
243
+ class="spinner"
244
+ width="16"
245
+ height="16"
246
+ viewBox="0 0 16 16"
247
+ fill="none"
248
+ xmlns="http://www.w3.org/2000/svg"
249
+ >
250
+ <circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="2" stroke-opacity="0.25"
251
+ ></circle>
252
+ <path
253
+ d="M14 8a6 6 0 0 0-6-6"
254
+ stroke="currentColor"
255
+ stroke-width="2"
256
+ stroke-linecap="round"></path>
257
+ </svg>
258
+ <span class="loading-text">Loading...</span>
259
+ </div>
260
+ </div>
261
+ </header>
262
+
263
+ <!-- Main Content: Chart + Table (stacked, full-width) -->
264
+ <div class="content-stack">
265
+ <!-- Daily Costs Chart (full width) -->
266
+ <div class="chart-panel">
267
+ <div class="panel-header">
268
+ <h3 class="panel-title">Daily Costs</h3>
269
+ </div>
270
+ <div class="chart-container">
271
+ <CostChart />
272
+ </div>
273
+ </div>
274
+
275
+ <!-- Daily Breakdown Table (full width) -->
276
+ <div class="table-panel">
277
+ <div class="panel-header">
278
+ <h3 class="panel-title">Daily Breakdown</h3>
279
+ </div>
280
+ <div class="table-container">
281
+ <CostTable />
282
+ </div>
283
+ </div>
284
+ </div>
285
+
286
+ <!-- Loading overlay -->
287
+ <div class="loading-overlay" id="loading-overlay" aria-hidden="true">
288
+ <div class="loading-spinner">
289
+ <div class="spinner-ring"></div>
290
+ <div class="spinner-ring spinner-ring--delayed"></div>
291
+ </div>
292
+ </div>
293
+ </section>
294
+
295
+ <style>
296
+ .daily-overview {
297
+ position: relative;
298
+ background: var(--usage-bg-card);
299
+ border: 1px solid var(--usage-border-default);
300
+ border-radius: var(--usage-radius-lg);
301
+ overflow: hidden;
302
+ margin-bottom: var(--usage-spacing-xl);
303
+ transition:
304
+ border-color 0.2s ease,
305
+ box-shadow 0.2s ease;
306
+ }
307
+
308
+ .daily-overview:hover {
309
+ border-color: var(--usage-border-hover, var(--usage-border-default));
310
+ box-shadow: 0 4px 24px -8px rgba(0, 0, 0, 0.08);
311
+ }
312
+
313
+ /* Geometric accent bar */
314
+ .accent-bar {
315
+ display: flex;
316
+ height: 4px;
317
+ background: var(--usage-border-subtle);
318
+ }
319
+
320
+ .accent-segment {
321
+ height: 100%;
322
+ transition: flex 0.4s cubic-bezier(0.4, 0, 0.2, 1);
323
+ }
324
+
325
+ .accent-segment--primary {
326
+ flex: 5;
327
+ background: var(--usage-resource-workers, #f59e0b);
328
+ }
329
+
330
+ .accent-segment--secondary {
331
+ flex: 3;
332
+ background: var(--usage-resource-aiGateway, #06b6d4);
333
+ }
334
+
335
+ .accent-segment--tertiary {
336
+ flex: 2;
337
+ background: var(--usage-resource-d1, #3b82f6);
338
+ }
339
+
340
+ /* Legacy Data Warning Banner */
341
+ .legacy-data-warning {
342
+ display: flex;
343
+ align-items: flex-start;
344
+ gap: var(--usage-spacing-sm);
345
+ padding: var(--usage-spacing-sm) var(--usage-spacing-md);
346
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
347
+ border-bottom: 1px solid #f59e0b;
348
+ color: #92400e;
349
+ }
350
+
351
+ .legacy-data-warning .warning-icon {
352
+ font-size: 1rem;
353
+ line-height: 1.5;
354
+ flex-shrink: 0;
355
+ }
356
+
357
+ .legacy-data-warning .warning-content {
358
+ flex: 1;
359
+ font-size: 0.8125rem;
360
+ line-height: 1.5;
361
+ }
362
+
363
+ .legacy-data-warning .warning-content strong {
364
+ display: block;
365
+ font-weight: 600;
366
+ margin-bottom: 0.125rem;
367
+ color: #78350f;
368
+ }
369
+
370
+ .legacy-data-warning .warning-content p {
371
+ margin: 0;
372
+ opacity: 0.9;
373
+ }
374
+
375
+ :global([data-theme='dark']) .legacy-data-warning,
376
+ :global(.dark) .legacy-data-warning {
377
+ background: linear-gradient(135deg, #422006 0%, #78350f 100%);
378
+ border-color: #b45309;
379
+ color: #fef3c7;
380
+ }
381
+
382
+ :global([data-theme='dark']) .legacy-data-warning .warning-content strong,
383
+ :global(.dark) .legacy-data-warning .warning-content strong {
384
+ color: #fde68a;
385
+ }
386
+
387
+ /* Header */
388
+ .section-header {
389
+ display: flex;
390
+ flex-direction: column;
391
+ gap: var(--usage-spacing-md);
392
+ padding: var(--usage-spacing-lg);
393
+ border-bottom: 1px solid var(--usage-border-subtle);
394
+ background: var(--usage-bg-secondary);
395
+ }
396
+
397
+ .header-row {
398
+ display: flex;
399
+ align-items: center;
400
+ justify-content: space-between;
401
+ gap: var(--usage-spacing-md);
402
+ flex-wrap: wrap;
403
+ }
404
+
405
+ .header-row--primary {
406
+ gap: var(--usage-spacing-lg);
407
+ }
408
+
409
+ .header-title {
410
+ display: flex;
411
+ flex-direction: column;
412
+ gap: var(--usage-spacing-xs);
413
+ flex-shrink: 0;
414
+ }
415
+
416
+ .section-title {
417
+ margin: 0;
418
+ font-size: 1.25rem;
419
+ font-weight: 600;
420
+ color: var(--usage-text-primary);
421
+ letter-spacing: -0.01em;
422
+ }
423
+
424
+ .section-subtitle {
425
+ font-size: 0.8125rem;
426
+ color: var(--usage-text-muted);
427
+ font-variant-numeric: tabular-nums;
428
+ }
429
+
430
+ .primary-filters {
431
+ display: flex;
432
+ align-items: center;
433
+ gap: var(--usage-spacing-md);
434
+ flex-wrap: wrap;
435
+ }
436
+
437
+ /* Stats Row */
438
+ .header-row--stats {
439
+ padding-top: var(--usage-spacing-sm);
440
+ border-top: 1px solid var(--usage-border-subtle);
441
+ }
442
+
443
+ .stat-pills {
444
+ display: flex;
445
+ gap: var(--usage-spacing-sm);
446
+ flex-wrap: wrap;
447
+ }
448
+
449
+ .stat-pill {
450
+ display: flex;
451
+ align-items: center;
452
+ gap: var(--usage-spacing-xs);
453
+ padding: var(--usage-spacing-xs) var(--usage-spacing-sm);
454
+ background: var(--usage-bg-primary);
455
+ border: 1px solid var(--usage-border-subtle);
456
+ border-radius: var(--usage-radius-md);
457
+ font-size: 0.8125rem;
458
+ }
459
+
460
+ .stat-pill--hero {
461
+ background: linear-gradient(135deg, #d97706 0%, #b45309 100%);
462
+ border-color: #d97706;
463
+ padding: var(--usage-spacing-sm) var(--usage-spacing-md);
464
+ }
465
+
466
+ .stat-pill.stat-pill--hero .stat-label,
467
+ .stat-pill.stat-pill--hero .stat-value {
468
+ color: #ffffff;
469
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
470
+ }
471
+
472
+ :global([data-theme='dark']) .stat-pill--hero {
473
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
474
+ border-color: #f59e0b;
475
+ }
476
+
477
+ :global([data-theme='dark']) .stat-pill.stat-pill--hero .stat-label,
478
+ :global([data-theme='dark']) .stat-pill.stat-pill--hero .stat-value {
479
+ color: #ffffff;
480
+ }
481
+
482
+ .stat-pill--muted {
483
+ opacity: 0.8;
484
+ }
485
+
486
+ .stat-label {
487
+ color: var(--usage-text-muted);
488
+ font-weight: 500;
489
+ font-size: 0.6875rem;
490
+ text-transform: uppercase;
491
+ letter-spacing: 0.03em;
492
+ }
493
+
494
+ .stat-value {
495
+ color: var(--usage-text-primary);
496
+ font-weight: 600;
497
+ font-variant-numeric: tabular-nums;
498
+ font-family: var(--usage-font-mono, 'JetBrains Mono', monospace);
499
+ }
500
+
501
+ .stat-value--large {
502
+ font-size: 1.125rem;
503
+ }
504
+
505
+ /* Header Actions */
506
+ .header-actions {
507
+ display: flex;
508
+ gap: var(--usage-spacing-xs);
509
+ }
510
+
511
+ .action-button {
512
+ display: inline-flex;
513
+ align-items: center;
514
+ gap: 0.375rem;
515
+ padding: 0.375rem 0.625rem;
516
+ background: var(--usage-bg-primary);
517
+ border: 1px solid var(--usage-border-primary);
518
+ border-radius: var(--usage-radius-sm);
519
+ color: var(--usage-text-secondary);
520
+ font-size: 0.75rem;
521
+ font-weight: 500;
522
+ cursor: pointer;
523
+ transition: all 0.15s ease;
524
+ text-decoration: none;
525
+ }
526
+
527
+ .action-button:hover {
528
+ background: var(--usage-bg-tertiary);
529
+ border-color: var(--usage-border-hover);
530
+ color: var(--usage-text-primary);
531
+ }
532
+
533
+ .action-button--primary {
534
+ background: var(--usage-accent-blue);
535
+ border-color: var(--usage-accent-blue);
536
+ color: #ffffff;
537
+ }
538
+
539
+ .action-button--primary:hover {
540
+ background: color-mix(in srgb, var(--usage-accent-blue) 85%, #000000);
541
+ border-color: color-mix(in srgb, var(--usage-accent-blue) 85%, #000000);
542
+ color: #ffffff;
543
+ }
544
+
545
+ .action-button:disabled,
546
+ .action-button[disabled] {
547
+ opacity: 0.6;
548
+ cursor: not-allowed;
549
+ }
550
+
551
+ /* Spinning animation for refresh icon */
552
+ @keyframes spin {
553
+ from {
554
+ transform: rotate(0deg);
555
+ }
556
+ to {
557
+ transform: rotate(360deg);
558
+ }
559
+ }
560
+
561
+ .refresh-icon.spinning {
562
+ animation: spin 1s linear infinite;
563
+ }
564
+
565
+ .action-label {
566
+ display: none;
567
+ }
568
+
569
+ @media (min-width: 768px) {
570
+ .action-label {
571
+ display: inline;
572
+ }
573
+ }
574
+
575
+ /* Tertiary Row */
576
+ .header-row--tertiary {
577
+ padding-top: var(--usage-spacing-sm);
578
+ border-top: 1px solid var(--usage-border-subtle);
579
+ }
580
+
581
+ .filter-toggles {
582
+ display: flex;
583
+ gap: var(--usage-spacing-xs);
584
+ flex-wrap: wrap;
585
+ }
586
+
587
+ .toggle-pill {
588
+ display: inline-flex;
589
+ align-items: center;
590
+ gap: 0.25rem;
591
+ padding: 0.25rem 0.5rem;
592
+ background: var(--usage-bg-primary);
593
+ border: 1px solid var(--usage-border-primary);
594
+ border-radius: 9999px;
595
+ color: var(--usage-text-secondary);
596
+ font-size: 0.6875rem;
597
+ font-weight: 500;
598
+ cursor: pointer;
599
+ transition: all 0.15s ease;
600
+ }
601
+
602
+ .toggle-pill:hover {
603
+ border-color: var(--usage-border-hover);
604
+ background: var(--usage-bg-tertiary);
605
+ }
606
+
607
+ .toggle-pill[aria-pressed='true'] {
608
+ background: color-mix(in srgb, var(--usage-accent-primary) 15%, transparent);
609
+ border-color: var(--usage-accent-primary);
610
+ color: var(--usage-accent-primary);
611
+ }
612
+
613
+ .toggle-icon {
614
+ font-weight: 700;
615
+ }
616
+
617
+ /* Auto-refresh */
618
+ .auto-refresh {
619
+ display: flex;
620
+ align-items: center;
621
+ gap: 0.375rem;
622
+ font-size: 0.6875rem;
623
+ color: var(--usage-text-muted);
624
+ }
625
+
626
+ .toggle-switch {
627
+ position: relative;
628
+ display: inline-block;
629
+ width: 32px;
630
+ height: 18px;
631
+ cursor: pointer;
632
+ }
633
+
634
+ .toggle-switch input {
635
+ opacity: 0;
636
+ width: 0;
637
+ height: 0;
638
+ }
639
+
640
+ .toggle-slider {
641
+ position: absolute;
642
+ inset: 0;
643
+ background-color: var(--usage-border-primary);
644
+ border-radius: 9999px;
645
+ transition: background-color 0.2s ease;
646
+ }
647
+
648
+ .toggle-slider::before {
649
+ content: '';
650
+ position: absolute;
651
+ height: 14px;
652
+ width: 14px;
653
+ left: 2px;
654
+ bottom: 2px;
655
+ background-color: white;
656
+ border-radius: 50%;
657
+ transition: transform 0.2s ease;
658
+ }
659
+
660
+ .toggle-switch input:checked + .toggle-slider {
661
+ background-color: var(--usage-accent-primary);
662
+ }
663
+
664
+ .toggle-switch input:checked + .toggle-slider::before {
665
+ transform: translateX(14px);
666
+ }
667
+
668
+ .toggle-text {
669
+ font-weight: 500;
670
+ }
671
+
672
+ .refresh-countdown {
673
+ font-variant-numeric: tabular-nums;
674
+ font-family: var(--usage-font-mono, monospace);
675
+ color: var(--usage-accent-primary);
676
+ }
677
+
678
+ /* Loading indicator */
679
+ .loading-indicator {
680
+ display: flex;
681
+ align-items: center;
682
+ gap: 0.375rem;
683
+ font-size: 0.75rem;
684
+ color: var(--usage-text-secondary);
685
+ margin-left: auto;
686
+ }
687
+
688
+ .spinner {
689
+ animation: spin 0.8s linear infinite;
690
+ color: var(--usage-accent-primary);
691
+ }
692
+
693
+ @keyframes spin {
694
+ to {
695
+ transform: rotate(360deg);
696
+ }
697
+ }
698
+
699
+ .loading-text {
700
+ font-weight: 500;
701
+ }
702
+
703
+ /* Content Stack (vertical layout) */
704
+ .content-stack {
705
+ display: flex;
706
+ flex-direction: column;
707
+ gap: var(--usage-spacing-lg);
708
+ padding: var(--usage-spacing-lg);
709
+ }
710
+
711
+ /* Panels */
712
+ .chart-panel,
713
+ .table-panel {
714
+ background: var(--usage-bg-secondary);
715
+ border: 1px solid var(--usage-border-subtle);
716
+ border-radius: var(--usage-radius-md);
717
+ overflow: hidden;
718
+ }
719
+
720
+ .panel-header {
721
+ padding: var(--usage-spacing-sm) var(--usage-spacing-md);
722
+ border-bottom: 1px solid var(--usage-border-subtle);
723
+ background: var(--usage-bg-tertiary);
724
+ }
725
+
726
+ .panel-title {
727
+ margin: 0;
728
+ font-size: 0.8125rem;
729
+ font-weight: 600;
730
+ color: var(--usage-text-primary);
731
+ text-transform: uppercase;
732
+ letter-spacing: 0.03em;
733
+ }
734
+
735
+ .chart-container {
736
+ min-height: 300px;
737
+ }
738
+
739
+ .table-container {
740
+ min-height: 300px;
741
+ }
742
+
743
+ /* Loading overlay */
744
+ .loading-overlay {
745
+ position: absolute;
746
+ inset: 0;
747
+ display: flex;
748
+ align-items: center;
749
+ justify-content: center;
750
+ background: var(--usage-bg-card);
751
+ opacity: 0;
752
+ visibility: hidden;
753
+ transition:
754
+ opacity 0.2s ease,
755
+ visibility 0.2s ease;
756
+ z-index: 10;
757
+ }
758
+
759
+ .loading-overlay[data-loading='true'] {
760
+ opacity: 0.9;
761
+ visibility: visible;
762
+ }
763
+
764
+ .loading-spinner {
765
+ position: relative;
766
+ width: 40px;
767
+ height: 40px;
768
+ }
769
+
770
+ .spinner-ring {
771
+ position: absolute;
772
+ inset: 0;
773
+ border: 2px solid var(--usage-border-subtle);
774
+ border-top-color: var(--usage-resource-workers, #f59e0b);
775
+ border-radius: 50%;
776
+ animation: spin 0.8s linear infinite;
777
+ }
778
+
779
+ .spinner-ring--delayed {
780
+ inset: 4px;
781
+ border-top-color: var(--usage-resource-aiGateway, #06b6d4);
782
+ animation-delay: -0.4s;
783
+ animation-direction: reverse;
784
+ }
785
+
786
+ /* Responsive */
787
+ @media (max-width: 767px) {
788
+ .section-header {
789
+ padding: var(--usage-spacing-md);
790
+ }
791
+
792
+ .header-row {
793
+ flex-direction: column;
794
+ align-items: stretch;
795
+ }
796
+
797
+ .header-title {
798
+ text-align: center;
799
+ }
800
+
801
+ .primary-filters {
802
+ justify-content: center;
803
+ }
804
+
805
+ .stat-pills {
806
+ justify-content: center;
807
+ }
808
+
809
+ .header-actions {
810
+ justify-content: center;
811
+ }
812
+
813
+ .filter-toggles {
814
+ justify-content: center;
815
+ }
816
+
817
+ .auto-refresh {
818
+ justify-content: center;
819
+ }
820
+
821
+ .content-stack {
822
+ padding: var(--usage-spacing-md);
823
+ }
824
+ }
825
+
826
+ /* Dark mode - comprehensive overrides */
827
+ :global([data-theme='dark']) .daily-overview,
828
+ :global(.dark) .daily-overview {
829
+ background: #161b22;
830
+ border-color: #30363d;
831
+ }
832
+
833
+ :global([data-theme='dark']) .daily-overview:hover,
834
+ :global(.dark) .daily-overview:hover {
835
+ border-color: #484f58;
836
+ box-shadow: 0 4px 24px -8px rgba(0, 0, 0, 0.4);
837
+ }
838
+
839
+ :global([data-theme='dark']) .section-header,
840
+ :global(.dark) .section-header {
841
+ background: #161b22;
842
+ border-color: #21262d;
843
+ }
844
+
845
+ :global([data-theme='dark']) .section-title,
846
+ :global(.dark) .section-title {
847
+ color: #e6edf3;
848
+ }
849
+
850
+ :global([data-theme='dark']) .section-subtitle,
851
+ :global(.dark) .section-subtitle {
852
+ color: #8b949e;
853
+ }
854
+
855
+ :global([data-theme='dark']) .header-row--stats,
856
+ :global(.dark) .header-row--stats {
857
+ border-color: #21262d;
858
+ }
859
+
860
+ :global([data-theme='dark']) .header-row--tertiary,
861
+ :global(.dark) .header-row--tertiary {
862
+ border-color: #21262d;
863
+ }
864
+
865
+ :global([data-theme='dark']) .stat-pill,
866
+ :global(.dark) .stat-pill {
867
+ background: #21262d;
868
+ border-color: #30363d;
869
+ }
870
+
871
+ :global([data-theme='dark']) .stat-pill .stat-label,
872
+ :global(.dark) .stat-pill .stat-label {
873
+ color: #8b949e;
874
+ }
875
+
876
+ :global([data-theme='dark']) .stat-pill .stat-value,
877
+ :global(.dark) .stat-pill .stat-value {
878
+ color: #e6edf3;
879
+ }
880
+
881
+ :global([data-theme='dark']) .toggle-pill,
882
+ :global(.dark) .toggle-pill {
883
+ background: #21262d;
884
+ border-color: #30363d;
885
+ color: #8b949e;
886
+ }
887
+
888
+ :global([data-theme='dark']) .toggle-pill:hover,
889
+ :global(.dark) .toggle-pill:hover {
890
+ background: #262c36;
891
+ border-color: #484f58;
892
+ }
893
+
894
+ :global([data-theme='dark']) .action-button,
895
+ :global(.dark) .action-button {
896
+ background: #21262d;
897
+ border-color: #30363d;
898
+ color: #8b949e;
899
+ }
900
+
901
+ :global([data-theme='dark']) .action-button:hover,
902
+ :global(.dark) .action-button:hover {
903
+ background: #262c36;
904
+ border-color: #484f58;
905
+ color: #e6edf3;
906
+ }
907
+
908
+ :global([data-theme='dark']) .action-button--primary,
909
+ :global(.dark) .action-button--primary {
910
+ background: #1f6feb;
911
+ border-color: #1f6feb;
912
+ color: #ffffff;
913
+ }
914
+
915
+ :global([data-theme='dark']) .action-button--primary:hover,
916
+ :global(.dark) .action-button--primary:hover {
917
+ background: #388bfd;
918
+ border-color: #388bfd;
919
+ color: #ffffff;
920
+ }
921
+
922
+ :global([data-theme='dark']) .auto-refresh,
923
+ :global(.dark) .auto-refresh {
924
+ color: #8b949e;
925
+ }
926
+
927
+ :global([data-theme='dark']) .toggle-slider,
928
+ :global(.dark) .toggle-slider {
929
+ background-color: #30363d;
930
+ }
931
+
932
+ :global([data-theme='dark']) .loading-indicator,
933
+ :global(.dark) .loading-indicator {
934
+ color: #8b949e;
935
+ }
936
+
937
+ :global([data-theme='dark']) .content-stack,
938
+ :global(.dark) .content-stack {
939
+ background: #161b22;
940
+ }
941
+
942
+ :global([data-theme='dark']) .chart-panel,
943
+ :global([data-theme='dark']) .table-panel,
944
+ :global(.dark) .chart-panel,
945
+ :global(.dark) .table-panel {
946
+ background: #21262d;
947
+ border-color: #30363d;
948
+ }
949
+
950
+ :global([data-theme='dark']) .panel-header,
951
+ :global(.dark) .panel-header {
952
+ background: #161b22;
953
+ border-color: #21262d;
954
+ }
955
+
956
+ :global([data-theme='dark']) .panel-title,
957
+ :global(.dark) .panel-title {
958
+ color: #e6edf3;
959
+ }
960
+
961
+ :global([data-theme='dark']) .loading-overlay,
962
+ :global(.dark) .loading-overlay {
963
+ background: rgba(22, 27, 34, 0.95);
964
+ }
965
+
966
+ :global([data-theme='dark']) .spinner-ring,
967
+ :global(.dark) .spinner-ring {
968
+ border-color: #30363d;
969
+ }
970
+
971
+ :global([data-theme='dark']) .accent-bar,
972
+ :global(.dark) .accent-bar {
973
+ background: #21262d;
974
+ }
975
+ </style>
976
+
977
+ <script>
978
+ import {
979
+ $dateRangeDisplay,
980
+ $totalCost,
981
+ $averageDailyCost,
982
+ $isLoading,
983
+ $hasLegacyData,
984
+ } from '../state/usageStore';
985
+ import { syncFromURL, initSubscriptions } from '../state/usageActions';
986
+
987
+ // Subscribe to date range changes
988
+ const displayEl = document.getElementById('date-range-display');
989
+ if (displayEl) {
990
+ $dateRangeDisplay.subscribe((value) => {
991
+ displayEl.textContent = value ? `Showing: ${value}` : 'No data';
992
+ });
993
+ }
994
+
995
+ // Subscribe to total cost changes
996
+ const totalEl = document.getElementById('total-cost-display');
997
+ if (totalEl) {
998
+ $totalCost.subscribe((value) => {
999
+ totalEl.textContent = `$${value.toFixed(2)}`;
1000
+ });
1001
+ }
1002
+
1003
+ // Subscribe to average cost changes
1004
+ const avgEl = document.getElementById('avg-cost-display');
1005
+ if (avgEl) {
1006
+ $averageDailyCost.subscribe((value) => {
1007
+ avgEl.textContent = `$${value.toFixed(2)}`;
1008
+ });
1009
+ }
1010
+
1011
+ // Subscribe to loading state
1012
+ const loadingEl = document.getElementById('loading-overlay');
1013
+ const filterLoadingEl = document.getElementById('filter-loading');
1014
+ if (loadingEl) {
1015
+ $isLoading.subscribe((loading) => {
1016
+ loadingEl.setAttribute('data-loading', String(loading));
1017
+ loadingEl.setAttribute('aria-hidden', String(!loading));
1018
+ if (filterLoadingEl) {
1019
+ filterLoadingEl.style.display = loading ? 'flex' : 'none';
1020
+ }
1021
+ });
1022
+ }
1023
+
1024
+ // Subscribe to legacy data warning
1025
+ const legacyWarningEl = document.getElementById('legacy-data-warning');
1026
+ if (legacyWarningEl) {
1027
+ $hasLegacyData.subscribe((hasLegacy) => {
1028
+ legacyWarningEl.style.display = hasLegacy ? 'flex' : 'none';
1029
+ });
1030
+ }
1031
+
1032
+ // Toggle buttons
1033
+ const onlyChangedBtn = document.getElementById('toggle-only-changed');
1034
+ const nonZeroCostBtn = document.getElementById('toggle-non-zero-cost');
1035
+
1036
+ function updateToggleUrl(onlyChanged: boolean, nonZeroCost: boolean) {
1037
+ const url = new URL(window.location.href);
1038
+ if (onlyChanged) {
1039
+ url.searchParams.set('onlyChanged', '1');
1040
+ } else {
1041
+ url.searchParams.delete('onlyChanged');
1042
+ }
1043
+ if (nonZeroCost) {
1044
+ url.searchParams.set('nonZeroCost', '1');
1045
+ } else {
1046
+ url.searchParams.delete('nonZeroCost');
1047
+ }
1048
+ window.history.pushState({}, '', url);
1049
+
1050
+ // Dispatch event for filter change
1051
+ document.dispatchEvent(
1052
+ new CustomEvent('filter-toggle-change', {
1053
+ detail: { onlyChanged, nonZeroCost },
1054
+ })
1055
+ );
1056
+ }
1057
+
1058
+ if (onlyChangedBtn) {
1059
+ onlyChangedBtn.addEventListener('click', () => {
1060
+ const isActive = onlyChangedBtn.getAttribute('aria-pressed') === 'true';
1061
+ onlyChangedBtn.setAttribute('aria-pressed', String(!isActive));
1062
+ const nonZeroActive = nonZeroCostBtn?.getAttribute('aria-pressed') === 'true';
1063
+ updateToggleUrl(!isActive, nonZeroActive || false);
1064
+ });
1065
+ }
1066
+
1067
+ if (nonZeroCostBtn) {
1068
+ nonZeroCostBtn.addEventListener('click', () => {
1069
+ const isActive = nonZeroCostBtn.getAttribute('aria-pressed') === 'true';
1070
+ nonZeroCostBtn.setAttribute('aria-pressed', String(!isActive));
1071
+ const onlyChangedActive = onlyChangedBtn?.getAttribute('aria-pressed') === 'true';
1072
+ updateToggleUrl(onlyChangedActive || false, !isActive);
1073
+ });
1074
+ }
1075
+
1076
+ // Sync store from URL and initialise subscriptions
1077
+ syncFromURL();
1078
+ initSubscriptions();
1079
+ </script>