@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,292 @@
1
+ /**
2
+ * CircuitBreakerPanel — Circuit breaker status at all 3 levels with manual toggles
3
+ *
4
+ * Shows global, per-project, and recently-tripped feature breaker state.
5
+ * Fetches GET /api/usage/circuit-breakers and POSTs trip/reset actions.
6
+ */
7
+ import { useState, useEffect, useCallback } from 'react';
8
+
9
+ // =============================================================================
10
+ // TYPES
11
+ // =============================================================================
12
+
13
+ interface BreakerState {
14
+ level: 'global' | 'project' | 'feature';
15
+ key: string;
16
+ label: string;
17
+ status: 'active' | 'stopped';
18
+ reason?: string;
19
+ integrated?: boolean;
20
+ }
21
+
22
+ interface CircuitBreakerData {
23
+ success: true;
24
+ breakers: BreakerState[];
25
+ recentlyTripped: BreakerState[];
26
+ }
27
+
28
+ // =============================================================================
29
+ // COMPONENT
30
+ // =============================================================================
31
+
32
+ export default function CircuitBreakerPanel() {
33
+ const [data, setData] = useState<CircuitBreakerData | null>(null);
34
+ const [error, setError] = useState<string | null>(null);
35
+ const [loading, setLoading] = useState(true);
36
+ const [pendingAction, setPendingAction] = useState<string | null>(null);
37
+ const [confirm, setConfirm] = useState<{ level: string; key: string; action: 'trip' | 'reset'; label: string } | null>(null);
38
+
39
+ const fetchData = useCallback(() => {
40
+ fetch('/api/usage/circuit-breakers')
41
+ .then((res) => res.json())
42
+ .then((json) => {
43
+ if (json.success) {
44
+ setData(json as CircuitBreakerData);
45
+ } else {
46
+ setError(json.error ?? 'Unknown error');
47
+ }
48
+ })
49
+ .catch((err) => setError(err.message))
50
+ .finally(() => setLoading(false));
51
+ }, []);
52
+
53
+ useEffect(() => {
54
+ fetchData();
55
+ }, [fetchData]);
56
+
57
+ const handleToggle = async (level: string, key: string, action: 'trip' | 'reset'): Promise<void> => {
58
+ const actionKey = `${level}:${key}`;
59
+ setPendingAction(actionKey);
60
+ setConfirm(null);
61
+
62
+ try {
63
+ const res = await fetch('/api/usage/circuit-breakers', {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({ level, key, action }),
67
+ });
68
+ const json = await res.json();
69
+ if (!json.success) {
70
+ setError(json.error ?? 'Toggle failed');
71
+ } else {
72
+ // Refresh state
73
+ fetchData();
74
+ }
75
+ } catch (err) {
76
+ setError(err instanceof Error ? err.message : 'Toggle failed');
77
+ } finally {
78
+ setPendingAction(null);
79
+ }
80
+ };
81
+
82
+ const requestToggle = (breaker: BreakerState): void => {
83
+ const action = breaker.status === 'active' ? 'trip' : 'reset';
84
+ setConfirm({ level: breaker.level, key: breaker.key, action, label: breaker.label });
85
+ };
86
+
87
+ if (loading) {
88
+ return (
89
+ <div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 shadow-sm">
90
+ <div className="animate-pulse space-y-3">
91
+ <div className="h-5 bg-gray-200 dark:bg-gray-700 rounded w-1/3" />
92
+ <div className="space-y-2">
93
+ {[...Array(4)].map((_, i) => (
94
+ <div key={i} className="h-8 bg-gray-200 dark:bg-gray-700 rounded" />
95
+ ))}
96
+ </div>
97
+ </div>
98
+ </div>
99
+ );
100
+ }
101
+
102
+ if (error && !data) {
103
+ return (
104
+ <div className="bg-white dark:bg-gray-800 border border-red-200 dark:border-red-800 rounded-lg p-4 shadow-sm">
105
+ <p className="text-sm text-red-600 dark:text-red-400">Failed to load circuit breakers: {error}</p>
106
+ </div>
107
+ );
108
+ }
109
+
110
+ if (!data) return null;
111
+
112
+ const globalBreaker = data.breakers.find((b) => b.level === 'global');
113
+ const projectBreakers = data.breakers.filter((b) => b.level === 'project');
114
+ const featureBreakers = data.recentlyTripped;
115
+
116
+ return (
117
+ <div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 shadow-sm">
118
+ {/* Header */}
119
+ <div className="flex items-center justify-between mb-4">
120
+ <h3 className="text-base font-semibold text-gray-900 dark:text-gray-100">
121
+ Circuit Breakers
122
+ </h3>
123
+ <button
124
+ onClick={fetchData}
125
+ className="text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
126
+ title="Refresh"
127
+ >
128
+ Refresh
129
+ </button>
130
+ </div>
131
+
132
+ {/* Confirmation Dialog */}
133
+ {confirm && (
134
+ <div className="mb-4 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg">
135
+ <p className="text-sm text-amber-800 dark:text-amber-200 mb-2">
136
+ {confirm.action === 'trip'
137
+ ? `Stop all traffic for "${confirm.label}"?`
138
+ : `Re-enable traffic for "${confirm.label}"?`}
139
+ </p>
140
+ <div className="flex gap-2">
141
+ <button
142
+ onClick={() => handleToggle(confirm.level, confirm.key, confirm.action)}
143
+ className={`px-3 py-1 text-xs font-medium rounded ${
144
+ confirm.action === 'trip'
145
+ ? 'bg-red-600 text-white hover:bg-red-700'
146
+ : 'bg-emerald-600 text-white hover:bg-emerald-700'
147
+ } transition-colors`}
148
+ >
149
+ {confirm.action === 'trip' ? 'Confirm Stop' : 'Confirm Reset'}
150
+ </button>
151
+ <button
152
+ onClick={() => setConfirm(null)}
153
+ className="px-3 py-1 text-xs font-medium text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 rounded hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
154
+ >
155
+ Cancel
156
+ </button>
157
+ </div>
158
+ </div>
159
+ )}
160
+
161
+ {/* Error banner (non-fatal) */}
162
+ {error && data && (
163
+ <div className="mb-3 p-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-xs text-red-600 dark:text-red-400">
164
+ {error}
165
+ </div>
166
+ )}
167
+
168
+ <div className="space-y-4">
169
+ {/* Global */}
170
+ {globalBreaker && (
171
+ <BreakerRow
172
+ breaker={globalBreaker}
173
+ icon="globe"
174
+ onToggle={requestToggle}
175
+ isPending={pendingAction === `global:${globalBreaker.key}`}
176
+ />
177
+ )}
178
+
179
+ {/* Projects */}
180
+ <div>
181
+ <p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
182
+ Projects
183
+ </p>
184
+ <div className="space-y-1.5">
185
+ {projectBreakers.map((b) => (
186
+ <BreakerRow
187
+ key={b.key}
188
+ breaker={b}
189
+ icon="project"
190
+ onToggle={requestToggle}
191
+ isPending={pendingAction === `project:${b.key}`}
192
+ />
193
+ ))}
194
+ </div>
195
+ </div>
196
+
197
+ {/* Recently Tripped Features */}
198
+ <div>
199
+ <p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
200
+ Recently Tripped Features (24h)
201
+ </p>
202
+ {featureBreakers.length === 0 ? (
203
+ <p className="text-xs text-gray-400 dark:text-gray-500 italic">None</p>
204
+ ) : (
205
+ <div className="space-y-1.5">
206
+ {featureBreakers.map((b) => (
207
+ <BreakerRow
208
+ key={b.key}
209
+ breaker={b}
210
+ icon="feature"
211
+ onToggle={requestToggle}
212
+ isPending={pendingAction === `feature:${b.key}`}
213
+ />
214
+ ))}
215
+ </div>
216
+ )}
217
+ </div>
218
+ </div>
219
+ </div>
220
+ );
221
+ }
222
+
223
+ // =============================================================================
224
+ // SUB-COMPONENT: BreakerRow
225
+ // =============================================================================
226
+
227
+ interface BreakerRowProps {
228
+ breaker: BreakerState;
229
+ icon: 'globe' | 'project' | 'feature';
230
+ onToggle: (breaker: BreakerState) => void;
231
+ isPending: boolean;
232
+ }
233
+
234
+ const ICONS: Record<string, string> = {
235
+ globe: '\uD83C\uDF10',
236
+ project: '\uD83D\uDCE6',
237
+ feature: '\u26A1',
238
+ };
239
+
240
+ function BreakerRow({ breaker, icon, onToggle, isPending }: BreakerRowProps) {
241
+ const isActive = breaker.status === 'active';
242
+ const isNotIntegrated = breaker.level === 'project' && breaker.integrated === false;
243
+
244
+ return (
245
+ <div className={`flex items-center justify-between py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${isNotIntegrated ? 'opacity-50' : ''}`}>
246
+ <div className="flex items-center gap-2 min-w-0">
247
+ <span className="text-sm flex-shrink-0">{ICONS[icon]}</span>
248
+ <span className={`text-sm truncate ${isNotIntegrated ? 'text-gray-400 dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}`}>
249
+ {breaker.label}
250
+ </span>
251
+ {isNotIntegrated && (
252
+ <span className="text-[10px] text-gray-400 dark:text-gray-500 flex-shrink-0">(no SDK)</span>
253
+ )}
254
+ {breaker.reason && (
255
+ <span className="text-xs text-gray-400 dark:text-gray-500 truncate" title={breaker.reason}>
256
+ ({breaker.reason})
257
+ </span>
258
+ )}
259
+ </div>
260
+ <div className="flex items-center gap-2 flex-shrink-0 ml-2">
261
+ {isNotIntegrated ? (
262
+ <span className="inline-flex items-center gap-1 text-xs text-gray-400 dark:text-gray-500">
263
+ <span className="w-1.5 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600" />
264
+ N/A
265
+ </span>
266
+ ) : (
267
+ <span className={`inline-flex items-center gap-1 text-xs font-medium ${
268
+ isActive
269
+ ? 'text-emerald-600 dark:text-emerald-400'
270
+ : 'text-red-600 dark:text-red-400'
271
+ }`}>
272
+ <span className={`w-1.5 h-1.5 rounded-full ${isActive ? 'bg-emerald-500' : 'bg-red-500'}`} />
273
+ {isActive ? 'Active' : 'Stopped'}
274
+ </span>
275
+ )}
276
+ <button
277
+ onClick={() => onToggle(breaker)}
278
+ disabled={isPending}
279
+ className={`px-2 py-0.5 text-xs rounded border transition-colors ${
280
+ isPending
281
+ ? 'opacity-50 cursor-wait'
282
+ : isActive
283
+ ? 'text-red-600 border-red-200 hover:bg-red-50 dark:text-red-400 dark:border-red-800 dark:hover:bg-red-900/20'
284
+ : 'text-emerald-600 border-emerald-200 hover:bg-emerald-50 dark:text-emerald-400 dark:border-emerald-800 dark:hover:bg-emerald-900/20'
285
+ }`}
286
+ >
287
+ {isPending ? '...' : isActive ? 'Trip' : 'Reset'}
288
+ </button>
289
+ </div>
290
+ </div>
291
+ );
292
+ }