@littlebearapps/platform-admin-sdk 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +4 -7
  2. package/dist/templates.d.ts +1 -1
  3. package/dist/templates.js +206 -4
  4. package/package.json +1 -1
  5. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  6. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  7. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  8. package/templates/full/dashboard/src/components/reports/DigestStats.tsx +151 -0
  9. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  10. package/templates/full/dashboard/src/components/reports/HealthTrendsReport.tsx +192 -0
  11. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  12. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  13. package/templates/full/dashboard/src/components/usage/AIModelBreakdown.tsx +364 -0
  14. package/templates/full/dashboard/src/components/usage/unified/Recommendations.tsx +149 -0
  15. package/templates/full/dashboard/src/lib/cloudflare/alerting.ts +486 -0
  16. package/templates/full/dashboard/src/lib/cloudflare/graphql.ts +4785 -0
  17. package/templates/full/dashboard/src/lib/cloudflare/project-registry.ts +451 -0
  18. package/templates/full/dashboard/src/lib/notifications/api.ts +197 -0
  19. package/templates/full/dashboard/src/lib/notifications/types.ts.hbs +97 -0
  20. package/templates/full/dashboard/src/lib/patterns/api.ts +120 -0
  21. package/templates/full/dashboard/src/lib/patterns/types.ts +127 -0
  22. package/templates/full/dashboard/src/lib/reports/types.ts +231 -0
  23. package/templates/full/dashboard/src/lib/search/api.ts +258 -0
  24. package/templates/full/dashboard/src/lib/search/types.ts.hbs +115 -0
  25. package/templates/full/dashboard/src/lib/settings/api.ts.hbs +201 -0
  26. package/templates/full/dashboard/src/lib/settings/types.ts.hbs +104 -0
  27. package/templates/full/dashboard/src/lib/usage/allowance-config.ts.hbs +547 -0
  28. package/templates/full/dashboard/src/lib/usage/providers.ts +331 -0
  29. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  30. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  31. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  32. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  33. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  34. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  35. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  36. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  37. package/templates/full/dashboard/src/pages/map.astro +561 -0
  38. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  39. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  40. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  41. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  42. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  43. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  44. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  45. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  46. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  47. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  48. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  49. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  50. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  51. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  52. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  53. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  54. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  55. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  56. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  57. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  58. package/templates/shared/dashboard/src/components/reports/ReportInfoButton.tsx +98 -0
  59. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  60. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  61. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  62. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  63. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  64. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  65. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  66. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  67. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  68. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  69. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  70. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  71. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  72. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  73. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  74. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  75. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  76. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  77. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  78. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  79. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  80. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  81. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  82. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  83. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  84. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  85. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  86. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  87. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  88. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  89. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  90. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  91. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  92. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  93. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  94. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  95. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  96. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  97. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  98. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  99. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  100. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  101. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  102. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  103. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  104. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  105. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  106. package/templates/shared/dashboard/src/components/usage/react/DashboardShell.tsx +263 -0
  107. package/templates/shared/dashboard/src/components/usage/react/StatusBadge.tsx +77 -0
  108. package/templates/shared/dashboard/src/components/usage/react/UsageChart.tsx +391 -0
  109. package/templates/shared/dashboard/src/components/usage/react/index.ts.hbs +30 -0
  110. package/templates/shared/dashboard/src/components/usage/react/types.ts +137 -0
  111. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  112. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  113. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  114. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  115. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  116. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  117. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  118. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  119. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  120. package/templates/shared/dashboard/src/components/usage/transformers.ts +478 -0
  121. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  122. package/templates/shared/dashboard/src/components/usage/unified/AlertBanner.tsx +172 -0
  123. package/templates/shared/dashboard/src/components/usage/unified/HeroCardsRow.tsx +757 -0
  124. package/templates/shared/dashboard/src/components/usage/unified/LiveHeader.tsx +169 -0
  125. package/templates/shared/dashboard/src/components/usage/unified/ProjectsTable.tsx +448 -0
  126. package/templates/shared/dashboard/src/components/usage/unified/ResourceBreakdown.tsx +236 -0
  127. package/templates/shared/dashboard/src/components/usage/unified/Sparkline.tsx +127 -0
  128. package/templates/shared/dashboard/src/components/usage/unified/UnifiedShell.tsx +893 -0
  129. package/templates/shared/dashboard/src/components/usage/unified/index.ts.hbs +50 -0
  130. package/templates/shared/dashboard/src/components/usage/unified/types.ts +416 -0
  131. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  132. package/templates/shared/dashboard/src/lib/cloudflare/analytics.ts +310 -0
  133. package/templates/shared/dashboard/src/lib/cloudflare/d1.ts +55 -0
  134. package/templates/shared/dashboard/src/lib/cloudflare/index.ts.hbs +120 -0
  135. package/templates/shared/dashboard/src/lib/infrastructure/types.ts +116 -0
  136. package/templates/shared/dashboard/src/lib/usage/fetchWithDedup.ts +101 -0
  137. package/templates/shared/dashboard/src/lib/usage/index.ts.hbs +12 -0
  138. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  139. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  140. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  141. package/templates/shared/tests/e2e/usage-api.test.ts +909 -0
  142. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  143. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  144. package/templates/shared/tests/helpers/mock-storage.ts +166 -0
  145. package/templates/shared/tests/integration/kv-cache.test.ts +252 -0
  146. package/templates/shared/tests/integration/platform-usage.test.ts +956 -0
  147. package/templates/shared/tests/unit/billing.test.ts +331 -0
  148. package/templates/shared/tests/unit/cloudflare/graphql.test.ts +217 -0
  149. package/templates/shared/tests/unit/components/usage-transformers.test.ts +473 -0
  150. package/templates/shared/tests/unit/control.test.ts +226 -0
  151. package/templates/shared/tests/unit/cost-calculator.test.ts +141 -0
  152. package/templates/shared/tests/unit/economics.test.ts +365 -0
  153. package/templates/shared/tests/unit/telemetry-sampling.test.ts +401 -0
  154. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  155. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  156. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  157. package/templates/standard/dashboard/src/components/reports/CircuitBreakerReport.tsx +474 -0
  158. package/templates/standard/dashboard/src/components/reports/CostTrendsReport.tsx +229 -0
  159. package/templates/standard/dashboard/src/components/reports/ErrorTrendsReport.tsx +244 -0
  160. package/templates/standard/dashboard/src/components/reports/ProjectHealthTable.tsx +251 -0
  161. package/templates/standard/dashboard/src/components/reports/WarningDigestsTable.tsx +298 -0
  162. package/templates/standard/dashboard/src/components/usage/react/UsageTable.tsx +385 -0
  163. package/templates/standard/dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx +305 -0
  164. package/templates/standard/dashboard/src/components/usage/unified/FeatureBudgets.tsx +472 -0
  165. package/templates/standard/dashboard/src/lib/errors/api.ts +84 -0
  166. package/templates/standard/dashboard/src/lib/errors/types.ts +75 -0
  167. package/templates/standard/dashboard/src/lib/infrastructure/api.ts +141 -0
  168. package/templates/standard/dashboard/src/lib/infrastructure/gatus.ts.hbs +112 -0
  169. package/templates/standard/dashboard/src/lib/services/proxy/index.ts +20 -0
  170. package/templates/standard/dashboard/src/lib/services/proxy/proxy.ts +244 -0
  171. package/templates/standard/dashboard/src/lib/services/proxy/types.ts +81 -0
  172. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  173. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  174. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  175. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  176. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  177. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  178. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  179. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  180. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  181. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
  182. package/templates/standard/tests/integration/platform-sentinel.test.ts +497 -0
  183. package/templates/standard/tests/unit/cloudflare/alerting.test.ts +480 -0
  184. package/templates/standard/tests/unit/error-collector/dedup.test.ts +350 -0
  185. package/templates/standard/tests/unit/error-collector/github.test.ts +187 -0
@@ -0,0 +1,669 @@
1
+ ---
2
+ /**
3
+ * CircuitBreakerStatus Component
4
+ *
5
+ * Displays the status of KV circuit breakers for all tracked projects.
6
+ * Shows:
7
+ * - Global stop flag status
8
+ * - Per-project circuit breaker status (Scout, Brand Copilot, Australian History MCP, Platform)
9
+ * - Adaptive sampling mode for platform-usage worker
10
+ * - D1 write usage and limits
11
+ *
12
+ * Data is populated via JavaScript after API fetch.
13
+ */
14
+
15
+ // Projects with circuit breakers
16
+ const projects = [
17
+ { id: 'scout', name: 'Scout', kvKey: 'PROJECT:SCOUT:STATUS' },
18
+ { id: 'brand-copilot', name: 'Brand Copilot', kvKey: 'PROJECT:BRAND-COPILOT:STATUS' },
19
+ {
20
+ id: 'australian-history-mcp',
21
+ name: 'Australian History MCP',
22
+ kvKey: 'PROJECT:AUSTRALIAN-HISTORY-MCP:STATUS',
23
+ },
24
+ { id: 'platform', name: 'Platform', kvKey: 'PROJECT:PLATFORM:STATUS' },
25
+ ];
26
+ ---
27
+
28
+ <div class="circuit-breaker-panel" role="region" aria-label="Circuit breaker status">
29
+ <div class="panel-header">
30
+ <h3 class="panel-title">Circuit Breaker Status</h3>
31
+ <div class="global-status" id="global-status">
32
+ <span class="global-dot" data-status="active"></span>
33
+ <span class="global-label">System Active</span>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- D1 Write Usage -->
38
+ <div class="d1-usage-section">
39
+ <div class="d1-header">
40
+ <span class="d1-label">D1 Writes (24h)</span>
41
+ <span id="d1-sampling-mode" class="sampling-badge" data-mode="full">FULL</span>
42
+ </div>
43
+ <div class="d1-bar-container">
44
+ <div id="d1-usage-bar" class="d1-bar" style="width: 0%;"></div>
45
+ <div class="d1-markers">
46
+ <div class="d1-marker" style="left: 60%;" title="HALF sampling (60%)"></div>
47
+ <div class="d1-marker" style="left: 80%;" title="QUARTER sampling (80%)"></div>
48
+ <div class="d1-marker" style="left: 90%;" title="MINIMAL sampling (90%)"></div>
49
+ </div>
50
+ </div>
51
+ <div class="d1-meta">
52
+ <span id="d1-usage-current">0</span>
53
+ <span class="d1-separator">/</span>
54
+ <span id="d1-usage-limit">1,000,000</span>
55
+ <span class="d1-percentage" id="d1-usage-pct">(0%)</span>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- Project Circuit Breakers -->
60
+ <div class="project-breakers">
61
+ {
62
+ projects.map((project) => (
63
+ <div class="breaker-row" data-project={project.id}>
64
+ <div class="breaker-identity">
65
+ <span class="breaker-dot" data-status="active" />
66
+ <span class="breaker-name">{project.name}</span>
67
+ </div>
68
+ <div class="breaker-status">
69
+ <span class="status-label">Active</span>
70
+ </div>
71
+ <div class="breaker-actions">
72
+ <button
73
+ type="button"
74
+ class="reset-btn"
75
+ data-project={project.id}
76
+ title="Reset circuit breaker"
77
+ style="display: none;"
78
+ >
79
+ Reset
80
+ </button>
81
+ </div>
82
+ <div class="breaker-kv-key">
83
+ <code class="kv-key">{project.kvKey}</code>
84
+ </div>
85
+ </div>
86
+ ))
87
+ }
88
+ </div>
89
+
90
+ <!-- Adaptive Sampling Info -->
91
+ <div class="sampling-info">
92
+ <div class="sampling-header">
93
+ <span class="sampling-title">Adaptive Sampling</span>
94
+ </div>
95
+ <div class="sampling-modes">
96
+ <div class="mode-item" data-mode="full">
97
+ <span class="mode-dot"></span>
98
+ <span class="mode-label">FULL</span>
99
+ <span class="mode-desc">&lt;60%: Hourly</span>
100
+ </div>
101
+ <div class="mode-item" data-mode="half">
102
+ <span class="mode-dot"></span>
103
+ <span class="mode-label">HALF</span>
104
+ <span class="mode-desc">60-80%: Every 2h</span>
105
+ </div>
106
+ <div class="mode-item" data-mode="quarter">
107
+ <span class="mode-dot"></span>
108
+ <span class="mode-label">QUARTER</span>
109
+ <span class="mode-desc">80-90%: Every 4h</span>
110
+ </div>
111
+ <div class="mode-item" data-mode="minimal">
112
+ <span class="mode-dot"></span>
113
+ <span class="mode-label">MINIMAL</span>
114
+ <span class="mode-desc">&gt;90%: Daily</span>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <!-- Last Updated -->
120
+ <div class="panel-footer">
121
+ <span id="cb-last-updated" class="last-updated">Last checked: --</span>
122
+ </div>
123
+ </div>
124
+
125
+ <style>
126
+ .circuit-breaker-panel {
127
+ background: var(--usage-bg-secondary);
128
+ border: 1px solid var(--usage-border-default);
129
+ border-radius: var(--usage-radius-xl);
130
+ padding: 1.25rem;
131
+ display: flex;
132
+ flex-direction: column;
133
+ gap: 1rem;
134
+ }
135
+
136
+ .panel-header {
137
+ display: flex;
138
+ justify-content: space-between;
139
+ align-items: center;
140
+ }
141
+
142
+ .panel-title {
143
+ font-size: 0.875rem;
144
+ font-weight: 600;
145
+ color: var(--usage-text-primary);
146
+ margin: 0;
147
+ }
148
+
149
+ .global-status {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 0.375rem;
153
+ padding: 0.25rem 0.5rem;
154
+ background: var(--usage-bg-tertiary);
155
+ border-radius: var(--usage-radius-md);
156
+ }
157
+
158
+ .global-dot {
159
+ width: 8px;
160
+ height: 8px;
161
+ border-radius: 50%;
162
+ transition: all var(--usage-transition-normal);
163
+ }
164
+
165
+ .global-dot[data-status='active'] {
166
+ background: var(--usage-status-ok);
167
+ box-shadow: 0 0 6px var(--usage-status-ok);
168
+ }
169
+
170
+ .global-dot[data-status='stopped'] {
171
+ background: var(--usage-status-critical);
172
+ box-shadow: 0 0 6px var(--usage-status-critical);
173
+ animation: pulse 1s ease-in-out infinite;
174
+ }
175
+
176
+ @keyframes pulse {
177
+ 0%,
178
+ 100% {
179
+ opacity: 1;
180
+ }
181
+ 50% {
182
+ opacity: 0.5;
183
+ }
184
+ }
185
+
186
+ .global-label {
187
+ font-size: 0.6875rem;
188
+ font-weight: 500;
189
+ color: var(--usage-text-secondary);
190
+ }
191
+
192
+ /* D1 Usage Section */
193
+ .d1-usage-section {
194
+ padding: 0.75rem;
195
+ background: var(--usage-bg-tertiary);
196
+ border-radius: var(--usage-radius-lg);
197
+ }
198
+
199
+ .d1-header {
200
+ display: flex;
201
+ justify-content: space-between;
202
+ align-items: center;
203
+ margin-bottom: 0.5rem;
204
+ }
205
+
206
+ .d1-label {
207
+ font-size: 0.6875rem;
208
+ font-weight: 600;
209
+ text-transform: uppercase;
210
+ letter-spacing: 0.03em;
211
+ color: var(--usage-text-muted);
212
+ }
213
+
214
+ .sampling-badge {
215
+ font-size: 0.625rem;
216
+ font-weight: 700;
217
+ padding: 0.125rem 0.375rem;
218
+ border-radius: var(--usage-radius-sm);
219
+ background: var(--usage-status-ok-bg);
220
+ color: var(--usage-status-ok);
221
+ }
222
+
223
+ .sampling-badge[data-mode='half'] {
224
+ background: var(--usage-status-warning-bg);
225
+ color: var(--usage-status-warning);
226
+ }
227
+
228
+ .sampling-badge[data-mode='quarter'] {
229
+ background: var(--usage-accent-purple);
230
+ color: white;
231
+ }
232
+
233
+ .sampling-badge[data-mode='minimal'] {
234
+ background: var(--usage-status-critical-bg);
235
+ color: var(--usage-status-critical);
236
+ }
237
+
238
+ .d1-bar-container {
239
+ position: relative;
240
+ height: 8px;
241
+ background: var(--usage-bg-hover);
242
+ border-radius: var(--usage-radius-sm);
243
+ overflow: visible;
244
+ }
245
+
246
+ .d1-bar {
247
+ height: 100%;
248
+ background: linear-gradient(
249
+ 90deg,
250
+ var(--usage-status-ok),
251
+ var(--usage-status-warning),
252
+ var(--usage-status-critical)
253
+ );
254
+ background-size: 167% 100%;
255
+ border-radius: var(--usage-radius-sm);
256
+ transition: width var(--usage-transition-slow);
257
+ }
258
+
259
+ .d1-markers {
260
+ position: absolute;
261
+ top: 0;
262
+ left: 0;
263
+ right: 0;
264
+ bottom: 0;
265
+ pointer-events: none;
266
+ }
267
+
268
+ .d1-marker {
269
+ position: absolute;
270
+ top: -2px;
271
+ bottom: -2px;
272
+ width: 1px;
273
+ background: var(--usage-text-muted);
274
+ opacity: 0.5;
275
+ }
276
+
277
+ .d1-meta {
278
+ display: flex;
279
+ align-items: center;
280
+ gap: 0.25rem;
281
+ margin-top: 0.375rem;
282
+ font-family: var(--usage-font-mono);
283
+ font-size: 0.6875rem;
284
+ color: var(--usage-text-secondary);
285
+ }
286
+
287
+ .d1-separator {
288
+ color: var(--usage-text-muted);
289
+ }
290
+
291
+ .d1-percentage {
292
+ color: var(--usage-text-muted);
293
+ margin-left: 0.25rem;
294
+ }
295
+
296
+ /* Project Breakers */
297
+ .project-breakers {
298
+ display: flex;
299
+ flex-direction: column;
300
+ gap: 0.5rem;
301
+ }
302
+
303
+ .breaker-row {
304
+ display: grid;
305
+ grid-template-columns: 1fr auto auto auto;
306
+ gap: 0.75rem;
307
+ align-items: center;
308
+ padding: 0.5rem 0.75rem;
309
+ background: var(--usage-bg-tertiary);
310
+ border-radius: var(--usage-radius-md);
311
+ transition: background var(--usage-transition-fast);
312
+ }
313
+
314
+ .breaker-row:hover {
315
+ background: var(--usage-bg-hover);
316
+ }
317
+
318
+ .breaker-identity {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 0.5rem;
322
+ }
323
+
324
+ .breaker-dot {
325
+ width: 8px;
326
+ height: 8px;
327
+ border-radius: 50%;
328
+ flex-shrink: 0;
329
+ }
330
+
331
+ .breaker-dot[data-status='active'] {
332
+ background: var(--usage-status-ok);
333
+ }
334
+
335
+ .breaker-dot[data-status='tripped'] {
336
+ background: var(--usage-status-critical);
337
+ animation: pulse 1s ease-in-out infinite;
338
+ }
339
+
340
+ .breaker-dot[data-status='degraded'] {
341
+ background: var(--usage-status-warning);
342
+ }
343
+
344
+ .breaker-name {
345
+ font-size: 0.75rem;
346
+ font-weight: 500;
347
+ color: var(--usage-text-primary);
348
+ }
349
+
350
+ .breaker-status {
351
+ text-align: right;
352
+ }
353
+
354
+ .status-label {
355
+ font-size: 0.6875rem;
356
+ font-weight: 600;
357
+ color: var(--usage-text-secondary);
358
+ }
359
+
360
+ .breaker-actions {
361
+ display: flex;
362
+ align-items: center;
363
+ }
364
+
365
+ .reset-btn {
366
+ padding: 0.25rem 0.5rem;
367
+ font-size: 0.625rem;
368
+ font-weight: 600;
369
+ text-transform: uppercase;
370
+ letter-spacing: 0.03em;
371
+ color: var(--usage-status-critical);
372
+ background: var(--usage-status-critical-bg);
373
+ border: 1px solid var(--usage-status-critical);
374
+ border-radius: var(--usage-radius-sm);
375
+ cursor: pointer;
376
+ transition: all var(--usage-transition-fast);
377
+ }
378
+
379
+ .reset-btn:hover {
380
+ background: var(--usage-status-critical);
381
+ color: white;
382
+ }
383
+
384
+ .reset-btn:disabled {
385
+ opacity: 0.5;
386
+ cursor: not-allowed;
387
+ }
388
+
389
+ .reset-btn.loading {
390
+ pointer-events: none;
391
+ opacity: 0.7;
392
+ }
393
+
394
+ .breaker-kv-key {
395
+ display: none; /* Hidden by default, shown on hover or expanded view */
396
+ }
397
+
398
+ .kv-key {
399
+ font-family: var(--usage-font-mono);
400
+ font-size: 0.625rem;
401
+ color: var(--usage-text-muted);
402
+ background: var(--usage-bg-primary);
403
+ padding: 0.125rem 0.25rem;
404
+ border-radius: var(--usage-radius-sm);
405
+ }
406
+
407
+ /* Sampling Info */
408
+ .sampling-info {
409
+ border-top: 1px solid var(--usage-border-subtle);
410
+ padding-top: 0.75rem;
411
+ }
412
+
413
+ .sampling-header {
414
+ margin-bottom: 0.5rem;
415
+ }
416
+
417
+ .sampling-title {
418
+ font-size: 0.625rem;
419
+ font-weight: 600;
420
+ text-transform: uppercase;
421
+ letter-spacing: 0.05em;
422
+ color: var(--usage-text-muted);
423
+ }
424
+
425
+ .sampling-modes {
426
+ display: grid;
427
+ grid-template-columns: repeat(4, 1fr);
428
+ gap: 0.5rem;
429
+ }
430
+
431
+ .mode-item {
432
+ display: flex;
433
+ flex-direction: column;
434
+ align-items: center;
435
+ gap: 0.25rem;
436
+ padding: 0.375rem;
437
+ background: var(--usage-bg-tertiary);
438
+ border-radius: var(--usage-radius-md);
439
+ opacity: 0.5;
440
+ transition: opacity var(--usage-transition-fast);
441
+ }
442
+
443
+ .mode-item.active {
444
+ opacity: 1;
445
+ background: var(--usage-bg-hover);
446
+ }
447
+
448
+ .mode-dot {
449
+ width: 6px;
450
+ height: 6px;
451
+ border-radius: 50%;
452
+ background: var(--usage-text-muted);
453
+ }
454
+
455
+ .mode-item[data-mode='full'] .mode-dot {
456
+ background: var(--usage-status-ok);
457
+ }
458
+ .mode-item[data-mode='half'] .mode-dot {
459
+ background: var(--usage-status-warning);
460
+ }
461
+ .mode-item[data-mode='quarter'] .mode-dot {
462
+ background: var(--usage-accent-purple);
463
+ }
464
+ .mode-item[data-mode='minimal'] .mode-dot {
465
+ background: var(--usage-status-critical);
466
+ }
467
+
468
+ .mode-label {
469
+ font-size: 0.625rem;
470
+ font-weight: 700;
471
+ color: var(--usage-text-primary);
472
+ }
473
+
474
+ .mode-desc {
475
+ font-size: 0.5625rem;
476
+ color: var(--usage-text-muted);
477
+ text-align: center;
478
+ }
479
+
480
+ /* Footer */
481
+ .panel-footer {
482
+ border-top: 1px solid var(--usage-border-subtle);
483
+ padding-top: 0.5rem;
484
+ }
485
+
486
+ .last-updated {
487
+ font-size: 0.625rem;
488
+ color: var(--usage-text-muted);
489
+ }
490
+
491
+ /* Responsive */
492
+ @media (max-width: 480px) {
493
+ .sampling-modes {
494
+ grid-template-columns: repeat(2, 1fr);
495
+ }
496
+
497
+ .breaker-row {
498
+ grid-template-columns: 1fr auto auto;
499
+ }
500
+
501
+ .breaker-kv-key {
502
+ display: none;
503
+ }
504
+
505
+ .reset-btn {
506
+ padding: 0.2rem 0.4rem;
507
+ font-size: 0.5625rem;
508
+ }
509
+ }
510
+ </style>
511
+
512
+ <script>
513
+ /**
514
+ * CircuitBreakerStatus JavaScript
515
+ *
516
+ * Updates circuit breaker display from API data.
517
+ */
518
+
519
+ interface CircuitBreakerData {
520
+ globalStop: boolean;
521
+ samplingMode: 'FULL' | 'HALF' | 'QUARTER' | 'MINIMAL';
522
+ d1Writes24h: number;
523
+ d1Limit: number;
524
+ projects: Array<{
525
+ id: string;
526
+ status: 'active' | 'tripped' | 'degraded';
527
+ label: string;
528
+ }>;
529
+ lastUpdated: string;
530
+ }
531
+
532
+ // Format large numbers
533
+ function formatNumber(n: number): string {
534
+ return n.toLocaleString();
535
+ }
536
+
537
+ // Update circuit breaker display
538
+ window.updateCircuitBreakerStatus = function (data: CircuitBreakerData) {
539
+ // Global status
540
+ const globalDot = document.querySelector('.global-dot') as HTMLElement;
541
+ const globalLabel = document.querySelector('.global-label') as HTMLElement;
542
+ if (globalDot && globalLabel) {
543
+ globalDot.setAttribute('data-status', data.globalStop ? 'stopped' : 'active');
544
+ globalLabel.textContent = data.globalStop ? 'GLOBAL STOP' : 'System Active';
545
+ }
546
+
547
+ // Sampling mode badge
548
+ const samplingBadge = document.getElementById('d1-sampling-mode');
549
+ if (samplingBadge) {
550
+ samplingBadge.setAttribute('data-mode', data.samplingMode.toLowerCase());
551
+ samplingBadge.textContent = data.samplingMode;
552
+ }
553
+
554
+ // D1 usage bar
555
+ const d1Bar = document.getElementById('d1-usage-bar');
556
+ const d1Current = document.getElementById('d1-usage-current');
557
+ const d1Limit = document.getElementById('d1-usage-limit');
558
+ const d1Pct = document.getElementById('d1-usage-pct');
559
+
560
+ const usagePct = (data.d1Writes24h / data.d1Limit) * 100;
561
+
562
+ if (d1Bar) {
563
+ d1Bar.style.width = `${Math.min(usagePct, 100)}%`;
564
+ }
565
+ if (d1Current) {
566
+ d1Current.textContent = formatNumber(data.d1Writes24h);
567
+ }
568
+ if (d1Limit) {
569
+ d1Limit.textContent = formatNumber(data.d1Limit);
570
+ }
571
+ if (d1Pct) {
572
+ d1Pct.textContent = `(${usagePct.toFixed(1)}%)`;
573
+ }
574
+
575
+ // Project statuses
576
+ for (const project of data.projects) {
577
+ const row = document.querySelector(`.breaker-row[data-project="${project.id}"]`);
578
+ if (!row) continue;
579
+
580
+ const dot = row.querySelector('.breaker-dot') as HTMLElement;
581
+ const label = row.querySelector('.status-label') as HTMLElement;
582
+ const resetBtn = row.querySelector('.reset-btn') as HTMLButtonElement;
583
+
584
+ if (dot) {
585
+ dot.setAttribute('data-status', project.status);
586
+ }
587
+ if (label) {
588
+ label.textContent = project.label;
589
+ }
590
+ // Show reset button only for paused/tripped projects
591
+ if (resetBtn) {
592
+ const isPausedOrTripped =
593
+ project.status === 'tripped' || project.label.toLowerCase() === 'paused';
594
+ resetBtn.style.display = isPausedOrTripped ? 'inline-block' : 'none';
595
+ }
596
+ }
597
+
598
+ // Sampling mode indicators
599
+ const modeItems = document.querySelectorAll('.mode-item');
600
+ modeItems.forEach((item) => {
601
+ const mode = (item as HTMLElement).dataset.mode?.toUpperCase();
602
+ if (mode === data.samplingMode) {
603
+ item.classList.add('active');
604
+ } else {
605
+ item.classList.remove('active');
606
+ }
607
+ });
608
+
609
+ // Last updated
610
+ const lastUpdatedEl = document.getElementById('cb-last-updated');
611
+ if (lastUpdatedEl) {
612
+ lastUpdatedEl.textContent = `Last checked: ${data.lastUpdated}`;
613
+ }
614
+ };
615
+
616
+ // Handle reset button clicks
617
+ async function handleResetClick(event: Event) {
618
+ const button = event.currentTarget as HTMLButtonElement;
619
+ const projectId = button.dataset.project;
620
+
621
+ if (!projectId) return;
622
+
623
+ // Confirm reset
624
+ const confirmed = confirm(`Reset circuit breaker for ${projectId}?`);
625
+ if (!confirmed) return;
626
+
627
+ // Show loading state
628
+ button.classList.add('loading');
629
+ button.textContent = '...';
630
+
631
+ try {
632
+ // Include credentials to pass Cloudflare Access JWT cookie
633
+ const response = await fetch('/api/usage/reset-circuit-breaker', {
634
+ method: 'POST',
635
+ headers: { 'Content-Type': 'application/json' },
636
+ body: JSON.stringify({ service: projectId }),
637
+ credentials: 'include',
638
+ });
639
+
640
+ const result = await response.json();
641
+
642
+ if (result.success) {
643
+ // Trigger refresh of circuit breaker data
644
+ window.dispatchEvent(new CustomEvent('usage:refresh-projects'));
645
+ button.style.display = 'none';
646
+ } else {
647
+ alert(`Failed to reset: ${result.error || 'Unknown error'}`);
648
+ }
649
+ } catch (error) {
650
+ console.error('[CircuitBreaker] Reset error:', error);
651
+ alert('Failed to reset circuit breaker');
652
+ } finally {
653
+ button.classList.remove('loading');
654
+ button.textContent = 'Reset';
655
+ }
656
+ }
657
+
658
+ // Attach click handlers to all reset buttons
659
+ document.querySelectorAll('.reset-btn').forEach((btn) => {
660
+ btn.addEventListener('click', handleResetClick);
661
+ });
662
+
663
+ // Type declaration
664
+ declare global {
665
+ interface Window {
666
+ updateCircuitBreakerStatus: (data: CircuitBreakerData) => void;
667
+ }
668
+ }
669
+ </script>