@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,618 @@
1
+ ---
2
+ /**
3
+ * ThresholdSettings Component
4
+ *
5
+ * Editable table for configuring alert thresholds per service type.
6
+ * Part of task-17.16: Configurable Alert Thresholds.
7
+ *
8
+ * Features:
9
+ * - Shows all service types with current thresholds
10
+ * - Inline editing of warning/high/critical percentages
11
+ * - Configure absolute maximum cost alerts
12
+ * - Enable/disable alerts per service
13
+ * - Save to KV via /usage/settings API
14
+ */
15
+
16
+ interface ServiceThreshold {
17
+ warningPct: number;
18
+ highPct: number;
19
+ criticalPct: number;
20
+ absoluteMax: number;
21
+ enabled: boolean;
22
+ }
23
+
24
+ interface Props {
25
+ thresholds: Record<string, ServiceThreshold>;
26
+ lastUpdated?: string;
27
+ }
28
+
29
+ const { thresholds, lastUpdated } = Astro.props;
30
+
31
+ // Service display labels
32
+ const serviceLabels: Record<string, string> = {
33
+ workers: 'Workers',
34
+ d1: 'D1 Database',
35
+ kv: 'KV Storage',
36
+ r2: 'R2 Storage',
37
+ durableObjects: 'Durable Objects',
38
+ vectorize: 'Vectorize',
39
+ aiGateway: 'AI Gateway',
40
+ pages: 'Pages',
41
+ queues: 'Queues',
42
+ workflows: 'Workflows',
43
+ };
44
+
45
+ // Service order for display
46
+ const serviceOrder = [
47
+ 'workers',
48
+ 'd1',
49
+ 'kv',
50
+ 'r2',
51
+ 'durableObjects',
52
+ 'vectorize',
53
+ 'aiGateway',
54
+ 'pages',
55
+ 'queues',
56
+ 'workflows',
57
+ ];
58
+ ---
59
+
60
+ <div class="threshold-settings" data-thresholds={JSON.stringify(thresholds)}>
61
+ <div class="settings-header">
62
+ <h2>Alert Threshold Configuration</h2>
63
+ {
64
+ lastUpdated && (
65
+ <span class="last-updated">Last updated: {new Date(lastUpdated).toLocaleString()}</span>
66
+ )
67
+ }
68
+ </div>
69
+
70
+ <div class="settings-description">
71
+ <p>
72
+ Configure when alerts trigger for each service. Percentage thresholds are relative to free
73
+ tier limits.
74
+ </p>
75
+ </div>
76
+
77
+ <div class="table-wrapper">
78
+ <table class="threshold-table">
79
+ <thead>
80
+ <tr>
81
+ <th class="col-service">Service</th>
82
+ <th class="col-enabled">Enabled</th>
83
+ <th class="col-warning">Warning (%)</th>
84
+ <th class="col-high">High (%)</th>
85
+ <th class="col-critical">Critical (%)</th>
86
+ <th class="col-absolute">Max Cost ($)</th>
87
+ </tr>
88
+ </thead>
89
+ <tbody>
90
+ {
91
+ serviceOrder.map((service) => {
92
+ const config = thresholds[service];
93
+ if (!config) return null;
94
+ return (
95
+ <tr data-service={service}>
96
+ <td class="col-service">
97
+ <span class="service-label">{serviceLabels[service] || service}</span>
98
+ </td>
99
+ <td class="col-enabled">
100
+ <label class="toggle-switch">
101
+ <input
102
+ type="checkbox"
103
+ name={`${service}-enabled`}
104
+ checked={config.enabled}
105
+ class="threshold-input"
106
+ data-field="enabled"
107
+ />
108
+ <span class="toggle-slider" />
109
+ </label>
110
+ </td>
111
+ <td class="col-warning">
112
+ <input
113
+ type="number"
114
+ name={`${service}-warningPct`}
115
+ value={config.warningPct}
116
+ min="0"
117
+ max="100"
118
+ step="5"
119
+ class="threshold-input number-input"
120
+ data-field="warningPct"
121
+ />
122
+ </td>
123
+ <td class="col-high">
124
+ <input
125
+ type="number"
126
+ name={`${service}-highPct`}
127
+ value={config.highPct}
128
+ min="0"
129
+ max="100"
130
+ step="5"
131
+ class="threshold-input number-input"
132
+ data-field="highPct"
133
+ />
134
+ </td>
135
+ <td class="col-critical">
136
+ <input
137
+ type="number"
138
+ name={`${service}-criticalPct`}
139
+ value={config.criticalPct}
140
+ min="0"
141
+ max="100"
142
+ step="5"
143
+ class="threshold-input number-input"
144
+ data-field="criticalPct"
145
+ />
146
+ </td>
147
+ <td class="col-absolute">
148
+ <div class="dollar-input">
149
+ <span class="dollar-sign">$</span>
150
+ <input
151
+ type="number"
152
+ name={`${service}-absoluteMax`}
153
+ value={config.absoluteMax}
154
+ min="0"
155
+ step="1"
156
+ class="threshold-input number-input"
157
+ data-field="absoluteMax"
158
+ />
159
+ </div>
160
+ </td>
161
+ </tr>
162
+ );
163
+ })
164
+ }
165
+ </tbody>
166
+ </table>
167
+ </div>
168
+
169
+ <div class="settings-actions">
170
+ <button type="button" id="reset-defaults" class="btn btn-secondary"> Reset to Defaults </button>
171
+ <button type="button" id="save-thresholds" class="btn btn-primary"> Save Changes </button>
172
+ </div>
173
+
174
+ <div id="save-status" class="save-status" role="status" aria-live="polite"></div>
175
+ </div>
176
+
177
+ <style>
178
+ .threshold-settings {
179
+ background: var(--card-bg, #fff);
180
+ border-radius: 8px;
181
+ padding: 1.5rem;
182
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
183
+ }
184
+
185
+ .settings-header {
186
+ display: flex;
187
+ justify-content: space-between;
188
+ align-items: center;
189
+ margin-bottom: 1rem;
190
+ flex-wrap: wrap;
191
+ gap: 0.5rem;
192
+ }
193
+
194
+ .settings-header h2 {
195
+ margin: 0;
196
+ font-size: 1.25rem;
197
+ font-weight: 600;
198
+ color: var(--text-primary, #1a1a1a);
199
+ }
200
+
201
+ .last-updated {
202
+ font-size: 0.75rem;
203
+ color: var(--text-muted, #666);
204
+ }
205
+
206
+ .settings-description {
207
+ margin-bottom: 1.5rem;
208
+ }
209
+
210
+ .settings-description p {
211
+ margin: 0;
212
+ font-size: 0.875rem;
213
+ color: var(--text-secondary, #444);
214
+ }
215
+
216
+ .table-wrapper {
217
+ overflow-x: auto;
218
+ margin-bottom: 1.5rem;
219
+ }
220
+
221
+ .threshold-table {
222
+ width: 100%;
223
+ border-collapse: collapse;
224
+ font-size: 0.875rem;
225
+ }
226
+
227
+ .threshold-table th,
228
+ .threshold-table td {
229
+ padding: 0.75rem;
230
+ text-align: left;
231
+ border-bottom: 1px solid var(--border-color, #e5e7eb);
232
+ }
233
+
234
+ .threshold-table th {
235
+ font-weight: 600;
236
+ color: var(--text-secondary, #444);
237
+ background: var(--table-header-bg, #f9fafb);
238
+ white-space: nowrap;
239
+ }
240
+
241
+ .threshold-table tbody tr:hover {
242
+ background: var(--row-hover, #f3f4f6);
243
+ }
244
+
245
+ .col-service {
246
+ min-width: 140px;
247
+ }
248
+
249
+ .col-enabled {
250
+ width: 80px;
251
+ text-align: center;
252
+ }
253
+
254
+ .col-warning,
255
+ .col-high,
256
+ .col-critical,
257
+ .col-absolute {
258
+ width: 100px;
259
+ }
260
+
261
+ .service-label {
262
+ font-weight: 500;
263
+ color: var(--text-primary, #1a1a1a);
264
+ }
265
+
266
+ /* Toggle Switch */
267
+ .toggle-switch {
268
+ position: relative;
269
+ display: inline-block;
270
+ width: 40px;
271
+ height: 22px;
272
+ }
273
+
274
+ .toggle-switch input {
275
+ opacity: 0;
276
+ width: 0;
277
+ height: 0;
278
+ }
279
+
280
+ .toggle-slider {
281
+ position: absolute;
282
+ cursor: pointer;
283
+ top: 0;
284
+ left: 0;
285
+ right: 0;
286
+ bottom: 0;
287
+ background-color: #ccc;
288
+ transition: 0.3s;
289
+ border-radius: 22px;
290
+ }
291
+
292
+ .toggle-slider::before {
293
+ position: absolute;
294
+ content: '';
295
+ height: 16px;
296
+ width: 16px;
297
+ left: 3px;
298
+ bottom: 3px;
299
+ background-color: white;
300
+ transition: 0.3s;
301
+ border-radius: 50%;
302
+ }
303
+
304
+ .toggle-switch input:checked + .toggle-slider {
305
+ background-color: #3b82f6;
306
+ }
307
+
308
+ .toggle-switch input:checked + .toggle-slider::before {
309
+ transform: translateX(18px);
310
+ }
311
+
312
+ /* Number Inputs */
313
+ .number-input {
314
+ width: 70px;
315
+ padding: 0.375rem 0.5rem;
316
+ border: 1px solid var(--input-border, #d1d5db);
317
+ border-radius: 4px;
318
+ font-size: 0.875rem;
319
+ text-align: right;
320
+ background: var(--input-bg, #fff);
321
+ color: var(--text-primary, #1a1a1a);
322
+ }
323
+
324
+ .number-input:focus {
325
+ outline: none;
326
+ border-color: #3b82f6;
327
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
328
+ }
329
+
330
+ .number-input:disabled {
331
+ opacity: 0.5;
332
+ cursor: not-allowed;
333
+ }
334
+
335
+ .dollar-input {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 0.25rem;
339
+ }
340
+
341
+ .dollar-sign {
342
+ color: var(--text-muted, #666);
343
+ font-size: 0.875rem;
344
+ }
345
+
346
+ /* Actions */
347
+ .settings-actions {
348
+ display: flex;
349
+ justify-content: flex-end;
350
+ gap: 0.75rem;
351
+ }
352
+
353
+ .btn {
354
+ padding: 0.5rem 1rem;
355
+ font-size: 0.875rem;
356
+ font-weight: 500;
357
+ border-radius: 6px;
358
+ cursor: pointer;
359
+ transition: all 0.2s;
360
+ border: none;
361
+ }
362
+
363
+ .btn-secondary {
364
+ background: var(--btn-secondary-bg, #f3f4f6);
365
+ color: var(--text-primary, #1a1a1a);
366
+ border: 1px solid var(--border-color, #e5e7eb);
367
+ }
368
+
369
+ .btn-secondary:hover {
370
+ background: var(--btn-secondary-hover, #e5e7eb);
371
+ }
372
+
373
+ .btn-primary {
374
+ background: #3b82f6;
375
+ color: white;
376
+ }
377
+
378
+ .btn-primary:hover {
379
+ background: #2563eb;
380
+ }
381
+
382
+ .btn:disabled {
383
+ opacity: 0.6;
384
+ cursor: not-allowed;
385
+ }
386
+
387
+ /* Save Status */
388
+ .save-status {
389
+ margin-top: 1rem;
390
+ padding: 0.75rem;
391
+ border-radius: 6px;
392
+ font-size: 0.875rem;
393
+ display: none;
394
+ }
395
+
396
+ .save-status.success {
397
+ display: block;
398
+ background: #d1fae5;
399
+ color: #065f46;
400
+ }
401
+
402
+ .save-status.error {
403
+ display: block;
404
+ background: #fee2e2;
405
+ color: #991b1b;
406
+ }
407
+
408
+ .save-status.loading {
409
+ display: block;
410
+ background: #e0e7ff;
411
+ color: #3730a3;
412
+ }
413
+
414
+ /* Dark mode */
415
+ :global(.dark) .threshold-settings {
416
+ background: var(--card-bg, #1f2937);
417
+ }
418
+
419
+ :global(.dark) .threshold-table th {
420
+ background: var(--table-header-bg, #374151);
421
+ }
422
+
423
+ :global(.dark) .number-input {
424
+ background: var(--input-bg, #374151);
425
+ border-color: var(--input-border, #4b5563);
426
+ color: #fff;
427
+ }
428
+
429
+ /* Mobile responsive */
430
+ @media (max-width: 768px) {
431
+ .threshold-table th,
432
+ .threshold-table td {
433
+ padding: 0.5rem;
434
+ font-size: 0.8rem;
435
+ }
436
+
437
+ .number-input {
438
+ width: 55px;
439
+ padding: 0.25rem;
440
+ font-size: 0.8rem;
441
+ }
442
+
443
+ .settings-actions {
444
+ flex-direction: column;
445
+ }
446
+
447
+ .btn {
448
+ width: 100%;
449
+ }
450
+ }
451
+ </style>
452
+
453
+ <script>
454
+ const DEFAULT_THRESHOLDS = {
455
+ workers: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 5, enabled: true },
456
+ d1: { warningPct: 40, highPct: 60, criticalPct: 80, absoluteMax: 20, enabled: true },
457
+ kv: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 5, enabled: true },
458
+ r2: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 20, enabled: true },
459
+ durableObjects: {
460
+ warningPct: 50,
461
+ highPct: 75,
462
+ criticalPct: 90,
463
+ absoluteMax: 10,
464
+ enabled: true,
465
+ },
466
+ vectorize: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 5, enabled: true },
467
+ aiGateway: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 0, enabled: false },
468
+ pages: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 5, enabled: true },
469
+ queues: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 5, enabled: true },
470
+ workflows: { warningPct: 50, highPct: 75, criticalPct: 90, absoluteMax: 0, enabled: false },
471
+ };
472
+
473
+ document.addEventListener('DOMContentLoaded', () => {
474
+ const container = document.querySelector('.threshold-settings');
475
+ if (!container) return;
476
+
477
+ const saveBtn = document.getElementById('save-thresholds');
478
+ const resetBtn = document.getElementById('reset-defaults');
479
+ const statusEl = document.getElementById('save-status');
480
+
481
+ function showStatus(message: string, type: 'success' | 'error' | 'loading') {
482
+ if (!statusEl) return;
483
+ statusEl.textContent = message;
484
+ statusEl.className = 'save-status ' + type;
485
+ }
486
+
487
+ function hideStatus() {
488
+ if (!statusEl) return;
489
+ statusEl.className = 'save-status';
490
+ statusEl.textContent = '';
491
+ }
492
+
493
+ function collectThresholds(): Record<string, Record<string, number | boolean>> {
494
+ const thresholds: Record<string, Record<string, number | boolean>> = {};
495
+ const rows = container?.querySelectorAll('tbody tr[data-service]') || [];
496
+
497
+ rows.forEach((row) => {
498
+ const service = row.getAttribute('data-service');
499
+ if (!service) return;
500
+
501
+ const inputs = row.querySelectorAll('.threshold-input');
502
+ const config: Record<string, number | boolean> = {};
503
+
504
+ inputs.forEach((input) => {
505
+ const field = input.getAttribute('data-field');
506
+ if (!field) return;
507
+
508
+ if (input instanceof HTMLInputElement) {
509
+ if (input.type === 'checkbox') {
510
+ config[field] = input.checked;
511
+ } else {
512
+ config[field] = parseFloat(input.value) || 0;
513
+ }
514
+ }
515
+ });
516
+
517
+ thresholds[service] = config;
518
+ });
519
+
520
+ return thresholds;
521
+ }
522
+
523
+ function applyThresholds(thresholds: Record<string, Record<string, number | boolean>>) {
524
+ Object.entries(thresholds).forEach(([service, config]) => {
525
+ const row = container?.querySelector(`tr[data-service="${service}"]`);
526
+ if (!row) return;
527
+
528
+ Object.entries(config).forEach(([field, value]) => {
529
+ const input = row.querySelector(`[data-field="${field}"]`);
530
+ if (input instanceof HTMLInputElement) {
531
+ if (input.type === 'checkbox') {
532
+ input.checked = Boolean(value);
533
+ } else {
534
+ input.value = String(value);
535
+ }
536
+ }
537
+ });
538
+ });
539
+ }
540
+
541
+ async function saveThresholds() {
542
+ if (saveBtn instanceof HTMLButtonElement) {
543
+ saveBtn.disabled = true;
544
+ }
545
+ showStatus('Saving...', 'loading');
546
+
547
+ try {
548
+ const thresholds = collectThresholds();
549
+ // Include credentials to pass Cloudflare Access JWT cookie
550
+ const response = await fetch('/api/usage/settings', {
551
+ method: 'PUT',
552
+ headers: { 'Content-Type': 'application/json' },
553
+ body: JSON.stringify({ thresholds }),
554
+ credentials: 'include',
555
+ });
556
+
557
+ const result = await response.json();
558
+
559
+ if (!response.ok || !result.success) {
560
+ throw new Error(result.message || 'Failed to save settings');
561
+ }
562
+
563
+ showStatus('Settings saved successfully!', 'success');
564
+ setTimeout(hideStatus, 3000);
565
+ } catch (error) {
566
+ const msg = error instanceof Error ? error.message : 'Unknown error';
567
+ showStatus('Error: ' + msg, 'error');
568
+ } finally {
569
+ if (saveBtn instanceof HTMLButtonElement) {
570
+ saveBtn.disabled = false;
571
+ }
572
+ }
573
+ }
574
+
575
+ function resetToDefaults() {
576
+ if (confirm('Reset all thresholds to default values?')) {
577
+ applyThresholds(DEFAULT_THRESHOLDS);
578
+ showStatus('Reset to defaults. Click Save to persist.', 'success');
579
+ setTimeout(hideStatus, 3000);
580
+ }
581
+ }
582
+
583
+ saveBtn?.addEventListener('click', saveThresholds);
584
+ resetBtn?.addEventListener('click', resetToDefaults);
585
+
586
+ // Disable inputs when service is disabled
587
+ container?.addEventListener('change', (e) => {
588
+ const target = e.target;
589
+ if (!(target instanceof HTMLInputElement)) return;
590
+
591
+ if (target.getAttribute('data-field') === 'enabled') {
592
+ const row = target.closest('tr');
593
+ if (!row) return;
594
+
595
+ const inputs = row.querySelectorAll('.number-input');
596
+ inputs.forEach((input) => {
597
+ if (input instanceof HTMLInputElement) {
598
+ input.disabled = !target.checked;
599
+ }
600
+ });
601
+ }
602
+ });
603
+
604
+ // Initialize disabled state
605
+ const checkboxes = container?.querySelectorAll('[data-field="enabled"]') || [];
606
+ checkboxes.forEach((checkbox) => {
607
+ if (checkbox instanceof HTMLInputElement && !checkbox.checked) {
608
+ const row = checkbox.closest('tr');
609
+ const inputs = row?.querySelectorAll('.number-input') || [];
610
+ inputs.forEach((input) => {
611
+ if (input instanceof HTMLInputElement) {
612
+ input.disabled = true;
613
+ }
614
+ });
615
+ }
616
+ });
617
+ });
618
+ </script>