@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,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>