@littlebearapps/platform-admin-sdk 1.5.0 → 2.1.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.
- package/README.md +2 -2
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +197 -2
- package/package.json +1 -1
- package/templates/full/dashboard/src/components/patterns/ActivePatterns.tsx +62 -0
- package/templates/full/dashboard/src/components/patterns/PatternTabs.tsx +116 -0
- package/templates/full/dashboard/src/components/patterns/SystemPatterns.tsx +52 -0
- package/templates/full/dashboard/src/components/patterns/index.ts +3 -0
- package/templates/full/dashboard/src/components/reports/DigestStats.tsx +151 -0
- package/templates/full/dashboard/src/components/reports/GapDetectionReport.tsx +69 -0
- package/templates/full/dashboard/src/components/reports/HealthTrendsReport.tsx +192 -0
- package/templates/full/dashboard/src/components/reports/SdkAuditReport.tsx +72 -0
- package/templates/full/dashboard/src/components/reports/index.ts +2 -0
- package/templates/full/dashboard/src/components/usage/AIModelBreakdown.tsx +364 -0
- package/templates/full/dashboard/src/components/usage/unified/Recommendations.tsx +149 -0
- package/templates/full/dashboard/src/lib/cloudflare/alerting.ts +486 -0
- package/templates/full/dashboard/src/lib/cloudflare/graphql.ts +4785 -0
- package/templates/full/dashboard/src/lib/cloudflare/project-registry.ts +451 -0
- package/templates/full/dashboard/src/lib/notifications/api.ts +197 -0
- package/templates/full/dashboard/src/lib/notifications/types.ts.hbs +97 -0
- package/templates/full/dashboard/src/lib/patterns/api.ts +120 -0
- package/templates/full/dashboard/src/lib/patterns/types.ts +127 -0
- package/templates/full/dashboard/src/lib/reports/types.ts +231 -0
- package/templates/full/dashboard/src/lib/search/api.ts +258 -0
- package/templates/full/dashboard/src/lib/search/types.ts.hbs +115 -0
- package/templates/full/dashboard/src/lib/settings/api.ts.hbs +201 -0
- package/templates/full/dashboard/src/lib/settings/types.ts.hbs +104 -0
- package/templates/full/dashboard/src/lib/usage/allowance-config.ts.hbs +547 -0
- package/templates/full/dashboard/src/lib/usage/providers.ts +331 -0
- package/templates/full/dashboard/src/pages/api/notifications/[id]/read.ts +37 -0
- package/templates/full/dashboard/src/pages/api/notifications/read-all.ts +28 -0
- package/templates/full/dashboard/src/pages/api/patterns/cache-refresh.ts +38 -0
- package/templates/full/dashboard/src/pages/api/patterns/discover.ts +36 -0
- package/templates/full/dashboard/src/pages/api/patterns/ready-for-review.ts +39 -0
- package/templates/full/dashboard/src/pages/api/patterns/stats.ts +39 -0
- package/templates/full/dashboard/src/pages/api/patterns/suggestions.ts +43 -0
- package/templates/full/dashboard/src/pages/api/reports/audit.ts +45 -0
- package/templates/full/dashboard/src/pages/api/reports/usage.ts +52 -0
- package/templates/full/dashboard/src/pages/api/search/reindex.ts +28 -0
- package/templates/full/dashboard/src/pages/api/search/stats.ts +27 -0
- package/templates/full/dashboard/src/pages/api/settings/index.ts +37 -0
- package/templates/full/dashboard/src/pages/api/settings/update.ts +41 -0
- package/templates/full/dashboard/src/pages/api/topology/index.ts +56 -0
- package/templates/full/scripts/ops/universal-backfill.ts +147 -0
- package/templates/shared/.github/workflows/contract-check.yml.hbs +42 -0
- package/templates/shared/.github/workflows/dashboard-deploy.yml.hbs +39 -0
- package/templates/shared/.github/workflows/security.yml +33 -0
- package/templates/shared/dashboard/src/components/Nav.astro.hbs +2 -0
- package/templates/shared/dashboard/src/components/infrastructure/AlertHistory.tsx +57 -0
- package/templates/shared/dashboard/src/components/infrastructure/InfrastructureStats.tsx +73 -0
- package/templates/shared/dashboard/src/components/infrastructure/ServiceRegistry.tsx +55 -0
- package/templates/shared/dashboard/src/components/infrastructure/UptimeStatus.tsx +56 -0
- package/templates/shared/dashboard/src/components/infrastructure/index.ts +4 -0
- package/templates/shared/dashboard/src/components/reports/ReportInfoButton.tsx +98 -0
- package/templates/shared/dashboard/src/components/ui/Breadcrumbs.tsx +27 -0
- package/templates/shared/dashboard/src/components/ui/EmptyState.tsx +26 -0
- package/templates/shared/dashboard/src/components/ui/ErrorBoundary.tsx +42 -0
- package/templates/shared/dashboard/src/components/ui/LoadingSkeleton.tsx +18 -0
- package/templates/shared/dashboard/src/components/ui/PageShell.tsx +26 -0
- package/templates/shared/dashboard/src/components/ui/Toast.tsx +44 -0
- package/templates/shared/dashboard/src/components/ui/index.ts +6 -0
- package/templates/shared/dashboard/src/components/usage/AnomaliesWidget.tsx +68 -0
- package/templates/shared/dashboard/src/components/usage/HourlyUsageChart.tsx +55 -0
- package/templates/shared/dashboard/src/components/usage/PlanAllowanceDashboard.tsx +67 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCostBreakdown.tsx +55 -0
- package/templates/shared/dashboard/src/components/usage/index.ts +4 -0
- package/templates/shared/dashboard/src/components/usage/react/DashboardShell.tsx +263 -0
- package/templates/shared/dashboard/src/components/usage/react/StatusBadge.tsx +77 -0
- package/templates/shared/dashboard/src/components/usage/react/UsageChart.tsx +391 -0
- package/templates/shared/dashboard/src/components/usage/react/index.ts.hbs +30 -0
- package/templates/shared/dashboard/src/components/usage/react/types.ts +137 -0
- package/templates/shared/dashboard/src/components/usage/transformers.ts +478 -0
- package/templates/shared/dashboard/src/components/usage/unified/AlertBanner.tsx +172 -0
- package/templates/shared/dashboard/src/components/usage/unified/HeroCardsRow.tsx +757 -0
- package/templates/shared/dashboard/src/components/usage/unified/LiveHeader.tsx +169 -0
- package/templates/shared/dashboard/src/components/usage/unified/ProjectsTable.tsx +448 -0
- package/templates/shared/dashboard/src/components/usage/unified/ResourceBreakdown.tsx +236 -0
- package/templates/shared/dashboard/src/components/usage/unified/Sparkline.tsx +127 -0
- package/templates/shared/dashboard/src/components/usage/unified/UnifiedShell.tsx +893 -0
- package/templates/shared/dashboard/src/components/usage/unified/index.ts.hbs +50 -0
- package/templates/shared/dashboard/src/components/usage/unified/types.ts +416 -0
- package/templates/shared/dashboard/src/lib/cloudflare/analytics.ts +310 -0
- package/templates/shared/dashboard/src/lib/cloudflare/costs.ts +21 -0
- package/templates/shared/dashboard/src/lib/cloudflare/d1.ts +55 -0
- package/templates/shared/dashboard/src/lib/cloudflare/index.ts.hbs +120 -0
- package/templates/shared/dashboard/src/lib/infrastructure/types.ts +116 -0
- package/templates/shared/dashboard/src/lib/usage/fetchWithDedup.ts +101 -0
- package/templates/shared/dashboard/src/lib/usage/index.ts.hbs +12 -0
- package/templates/shared/dashboard/src/pages/api/costs/overview.ts +65 -0
- package/templates/shared/dashboard/src/pages/api/costs/providers.ts +47 -0
- package/templates/shared/dashboard/src/pages/api/infrastructure/services.ts +55 -0
- package/templates/shared/dashboard/src/pages/api/infrastructure/stats.ts +99 -0
- package/templates/shared/dashboard/src/pages/api/usage/allowances.ts +56 -0
- package/templates/shared/dashboard/src/pages/api/usage/anomalies.ts +45 -0
- package/templates/shared/dashboard/src/pages/api/usage/billing.ts +53 -0
- package/templates/shared/dashboard/src/pages/api/usage/granular.ts +50 -0
- package/templates/shared/dashboard/src/pages/api/usage/hourly.ts +45 -0
- package/templates/shared/dashboard/src/pages/api/usage/projects.ts +51 -0
- package/templates/shared/dashboard/src/pages/api/user/identity.ts +11 -0
- package/templates/shared/dashboard/src/pages/settings/notifications.astro +34 -0
- package/templates/shared/dashboard/src/pages/settings/thresholds.astro +39 -0
- package/templates/shared/dashboard/src/pages/settings/usage.astro +28 -0
- package/templates/shared/docs/architecture.md +89 -0
- package/templates/shared/docs/post-deploy-runbook.md +126 -0
- package/templates/shared/docs/troubleshooting.md +91 -0
- package/templates/shared/package.json.hbs +5 -0
- package/templates/shared/scripts/ops/backfill-cloudflare-daily.ts +145 -0
- package/templates/shared/scripts/ops/backfill-monthly-rollups.ts +125 -0
- package/templates/shared/scripts/ops/validate-controls.js +141 -0
- package/templates/shared/tests/contract/validate-schemas.test.ts +130 -0
- package/templates/shared/tests/e2e/usage-api.test.ts +909 -0
- package/templates/shared/tests/fixtures/telemetry-envelope-invalid.json +9 -0
- package/templates/shared/tests/fixtures/telemetry-envelope-valid.json +27 -0
- package/templates/shared/tests/helpers/mock-d1.ts +61 -0
- package/templates/shared/tests/helpers/mock-kv.ts +37 -0
- package/templates/shared/tests/helpers/mock-storage.ts +166 -0
- package/templates/shared/tests/integration/kv-cache.test.ts +252 -0
- package/templates/shared/tests/integration/platform-usage.test.ts +956 -0
- package/templates/shared/tests/unit/billing.test.ts +331 -0
- package/templates/shared/tests/unit/cloudflare/graphql.test.ts +217 -0
- package/templates/shared/tests/unit/components/usage-transformers.test.ts +473 -0
- package/templates/shared/tests/unit/control.test.ts +226 -0
- package/templates/shared/tests/unit/cost-calculator.test.ts +141 -0
- package/templates/shared/tests/unit/economics.test.ts +365 -0
- package/templates/shared/tests/unit/telemetry-sampling.test.ts +401 -0
- package/templates/shared/tests/unit/workers/batch-persistence.test.ts +133 -0
- package/templates/shared/tests/unit/workers/budget-enforcement.test.ts +214 -0
- package/templates/shared/vitest.config.ts +18 -0
- package/templates/standard/dashboard/src/components/health/CircuitBreakerEvents.tsx +69 -0
- package/templates/standard/dashboard/src/components/health/CircuitBreakerPanel.tsx +97 -0
- package/templates/standard/dashboard/src/components/health/index.ts +2 -0
- package/templates/standard/dashboard/src/components/reports/CircuitBreakerReport.tsx +474 -0
- package/templates/standard/dashboard/src/components/reports/CostTrendsReport.tsx +229 -0
- package/templates/standard/dashboard/src/components/reports/ErrorTrendsReport.tsx +244 -0
- package/templates/standard/dashboard/src/components/reports/ProjectHealthTable.tsx +251 -0
- package/templates/standard/dashboard/src/components/reports/WarningDigestsTable.tsx +298 -0
- package/templates/standard/dashboard/src/components/usage/react/UsageTable.tsx +385 -0
- package/templates/standard/dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx +305 -0
- package/templates/standard/dashboard/src/components/usage/unified/FeatureBudgets.tsx +472 -0
- package/templates/standard/dashboard/src/lib/errors/api.ts +84 -0
- package/templates/standard/dashboard/src/lib/errors/types.ts +75 -0
- package/templates/standard/dashboard/src/lib/infrastructure/api.ts +141 -0
- package/templates/standard/dashboard/src/lib/infrastructure/gatus.ts.hbs +112 -0
- package/templates/standard/dashboard/src/lib/services/proxy/index.ts +20 -0
- package/templates/standard/dashboard/src/lib/services/proxy/proxy.ts +244 -0
- package/templates/standard/dashboard/src/lib/services/proxy/types.ts +81 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/mute.ts +49 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/resolve.ts +36 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint].ts +55 -0
- package/templates/standard/dashboard/src/pages/api/health/audit-history.ts +37 -0
- package/templates/standard/dashboard/src/pages/circuit-breakers.astro +13 -0
- package/templates/standard/tests/integration/platform-sentinel.test.ts +497 -0
- package/templates/standard/tests/unit/cloudflare/alerting.test.ts +480 -0
- package/templates/standard/tests/unit/error-collector/capture.test.ts +106 -0
- package/templates/standard/tests/unit/error-collector/dedup.test.ts +350 -0
- package/templates/standard/tests/unit/error-collector/fingerprint.test.ts +155 -0
- package/templates/standard/tests/unit/error-collector/github.test.ts +187 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DashboardShell Component
|
|
3
|
+
* Industrial Command Center orchestrator - data fetching, state, auto-refresh
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
7
|
+
import { Activity, RefreshCw, AlertTriangle } from 'lucide-react';
|
|
8
|
+
import { clsx } from 'clsx';
|
|
9
|
+
import {
|
|
10
|
+
type Period,
|
|
11
|
+
type UsageRow,
|
|
12
|
+
type ProjectStatus,
|
|
13
|
+
type StatusResponse,
|
|
14
|
+
type QueryResponse,
|
|
15
|
+
} from './types';
|
|
16
|
+
import { UsageChart } from './UsageChart';
|
|
17
|
+
import { UsageTable } from './UsageTable';
|
|
18
|
+
|
|
19
|
+
interface DashboardShellProps {
|
|
20
|
+
initialPeriod?: Period;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const REFRESH_INTERVAL = 60_000; // 60 seconds
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Period button component
|
|
27
|
+
*/
|
|
28
|
+
function PeriodButton({
|
|
29
|
+
period,
|
|
30
|
+
currentPeriod,
|
|
31
|
+
onClick,
|
|
32
|
+
}: {
|
|
33
|
+
period: Period;
|
|
34
|
+
currentPeriod: Period;
|
|
35
|
+
onClick: (p: Period) => void;
|
|
36
|
+
}) {
|
|
37
|
+
const isActive = period === currentPeriod;
|
|
38
|
+
return (
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={() => onClick(period)}
|
|
42
|
+
className={clsx(
|
|
43
|
+
'px-3 py-1.5 text-xs font-mono font-semibold uppercase tracking-wider rounded-sm transition-all',
|
|
44
|
+
isActive
|
|
45
|
+
? 'bg-gray-200 dark:bg-slate-700 text-gray-900 dark:text-slate-100 shadow-inner'
|
|
46
|
+
: 'bg-gray-100 dark:bg-slate-800 text-gray-600 dark:text-slate-400 hover:bg-gray-200 dark:hover:bg-slate-700 hover:text-gray-800 dark:hover:text-slate-300'
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{period}
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Live indicator with pulse
|
|
56
|
+
*/
|
|
57
|
+
function LiveIndicator({ isRefreshing }: { isRefreshing: boolean }) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex items-center gap-2">
|
|
60
|
+
{isRefreshing ? (
|
|
61
|
+
<RefreshCw className="w-3.5 h-3.5 text-gray-500 dark:text-slate-400 animate-spin" />
|
|
62
|
+
) : (
|
|
63
|
+
<span className="relative flex h-2.5 w-2.5">
|
|
64
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
|
|
65
|
+
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" />
|
|
66
|
+
</span>
|
|
67
|
+
)}
|
|
68
|
+
<span className="text-xs font-mono text-gray-500 dark:text-slate-500 uppercase tracking-wider">
|
|
69
|
+
{isRefreshing ? 'Updating' : 'Live'}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Error state component
|
|
77
|
+
*/
|
|
78
|
+
function ErrorState({ message, onRetry }: { message: string; onRetry: () => void }) {
|
|
79
|
+
return (
|
|
80
|
+
<div className="bg-rose-500/10 border border-rose-500/30 rounded-sm p-6 flex items-start gap-4">
|
|
81
|
+
<AlertTriangle className="w-5 h-5 text-rose-400 flex-shrink-0 mt-0.5" />
|
|
82
|
+
<div className="flex-1">
|
|
83
|
+
<h3 className="text-rose-200 font-semibold text-sm">Failed to load data</h3>
|
|
84
|
+
<p className="text-rose-300/80 text-xs mt-1 font-mono">{message}</p>
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={onRetry}
|
|
88
|
+
className="mt-3 px-3 py-1.5 bg-rose-500/20 hover:bg-rose-500/30 text-rose-200 text-xs font-mono rounded-sm transition-colors"
|
|
89
|
+
>
|
|
90
|
+
Retry
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Loading skeleton
|
|
99
|
+
*/
|
|
100
|
+
function LoadingSkeleton() {
|
|
101
|
+
return (
|
|
102
|
+
<div className="space-y-6 animate-pulse">
|
|
103
|
+
{/* Header skeleton */}
|
|
104
|
+
<div className="flex justify-between items-center">
|
|
105
|
+
<div className="h-8 bg-gray-200 dark:bg-slate-800 rounded w-48" />
|
|
106
|
+
<div className="h-8 bg-gray-200 dark:bg-slate-800 rounded w-32" />
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Chart skeleton */}
|
|
110
|
+
<div className="space-y-3">
|
|
111
|
+
<div className="h-5 bg-gray-200 dark:bg-slate-800 rounded w-36" />
|
|
112
|
+
<div className="h-64 bg-gray-100 dark:bg-slate-900 rounded-sm border border-gray-200 dark:border-slate-800" />
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Table skeleton */}
|
|
116
|
+
<div className="space-y-3">
|
|
117
|
+
<div className="h-5 bg-gray-200 dark:bg-slate-800 rounded w-24" />
|
|
118
|
+
<div className="h-48 bg-gray-100 dark:bg-slate-900 rounded-sm border border-gray-200 dark:border-slate-800" />
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function DashboardShell({ initialPeriod = '24h' }: DashboardShellProps) {
|
|
125
|
+
const [period, setPeriod] = useState<Period>(initialPeriod);
|
|
126
|
+
const [loading, setLoading] = useState(true);
|
|
127
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
128
|
+
const [error, setError] = useState<string | null>(null);
|
|
129
|
+
const [usageData, setUsageData] = useState<UsageRow[]>([]);
|
|
130
|
+
const [statusMap, setStatusMap] = useState<Record<string, ProjectStatus>>({});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Fetch data from both endpoints
|
|
134
|
+
*/
|
|
135
|
+
const fetchData = useCallback(
|
|
136
|
+
async (isBackground = false) => {
|
|
137
|
+
if (isBackground) {
|
|
138
|
+
setIsRefreshing(true);
|
|
139
|
+
} else {
|
|
140
|
+
setLoading(true);
|
|
141
|
+
setError(null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const [statusRes, queryRes] = await Promise.all([
|
|
146
|
+
fetch(`/api/usage/status?period=${period}`),
|
|
147
|
+
fetch(`/api/usage/query?period=${period}`),
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
if (!statusRes.ok) {
|
|
151
|
+
throw new Error(`Status endpoint failed: ${statusRes.status}`);
|
|
152
|
+
}
|
|
153
|
+
if (!queryRes.ok) {
|
|
154
|
+
throw new Error(`Query endpoint failed: ${queryRes.status}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const statusData: StatusResponse = await statusRes.json();
|
|
158
|
+
const queryData: QueryResponse = await queryRes.json();
|
|
159
|
+
|
|
160
|
+
if (!statusData.success || !queryData.success) {
|
|
161
|
+
throw new Error('API returned unsuccessful response');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
setStatusMap(statusData.projects);
|
|
165
|
+
setUsageData(queryData.data);
|
|
166
|
+
setError(null);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (!isBackground) {
|
|
169
|
+
setError(err instanceof Error ? err.message : 'Unknown error occurred');
|
|
170
|
+
}
|
|
171
|
+
console.error('[DashboardShell] Fetch error:', err);
|
|
172
|
+
} finally {
|
|
173
|
+
setLoading(false);
|
|
174
|
+
setIsRefreshing(false);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
[period]
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Handle period change - update URL and refetch
|
|
182
|
+
*/
|
|
183
|
+
const handlePeriodChange = useCallback((newPeriod: Period) => {
|
|
184
|
+
setPeriod(newPeriod);
|
|
185
|
+
|
|
186
|
+
// Update URL without reload
|
|
187
|
+
const url = new URL(window.location.href);
|
|
188
|
+
url.searchParams.set('period', newPeriod);
|
|
189
|
+
history.replaceState(null, '', url.toString());
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
// Initial fetch and period change
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
fetchData();
|
|
195
|
+
}, [fetchData]);
|
|
196
|
+
|
|
197
|
+
// Auto-refresh interval
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
const interval = setInterval(() => {
|
|
200
|
+
fetchData(true);
|
|
201
|
+
}, REFRESH_INTERVAL);
|
|
202
|
+
|
|
203
|
+
return () => clearInterval(interval);
|
|
204
|
+
}, [fetchData]);
|
|
205
|
+
|
|
206
|
+
if (loading) {
|
|
207
|
+
return (
|
|
208
|
+
<div className="p-6 bg-gray-50 dark:bg-slate-950 min-h-screen">
|
|
209
|
+
<LoadingSkeleton />
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (error) {
|
|
215
|
+
return (
|
|
216
|
+
<div className="p-6 bg-gray-50 dark:bg-slate-950 min-h-screen">
|
|
217
|
+
<ErrorState message={error} onRetry={() => fetchData()} />
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<div className="p-6 bg-gray-50 dark:bg-slate-950 min-h-screen space-y-6">
|
|
224
|
+
{/* Header */}
|
|
225
|
+
<header className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
226
|
+
<div className="flex items-center gap-3">
|
|
227
|
+
<Activity className="w-5 h-5 text-gray-600 dark:text-slate-400" />
|
|
228
|
+
<h1 className="text-xl font-semibold text-gray-900 dark:text-slate-100 tracking-tight">
|
|
229
|
+
Usage Monitor
|
|
230
|
+
</h1>
|
|
231
|
+
<LiveIndicator isRefreshing={isRefreshing} />
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<div className="flex items-center gap-2">
|
|
235
|
+
<PeriodButton period="24h" currentPeriod={period} onClick={handlePeriodChange} />
|
|
236
|
+
<PeriodButton period="7d" currentPeriod={period} onClick={handlePeriodChange} />
|
|
237
|
+
</div>
|
|
238
|
+
</header>
|
|
239
|
+
|
|
240
|
+
{/* Chart section */}
|
|
241
|
+
<section>
|
|
242
|
+
<h2 className="text-sm font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider mb-3">
|
|
243
|
+
Activity Timeline
|
|
244
|
+
</h2>
|
|
245
|
+
<UsageChart data={usageData} loading={isRefreshing && usageData.length === 0} />
|
|
246
|
+
</section>
|
|
247
|
+
|
|
248
|
+
{/* Table section */}
|
|
249
|
+
<section>
|
|
250
|
+
<h2 className="text-sm font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider mb-3">
|
|
251
|
+
Projects
|
|
252
|
+
</h2>
|
|
253
|
+
<UsageTable
|
|
254
|
+
data={usageData}
|
|
255
|
+
statusMap={statusMap}
|
|
256
|
+
loading={isRefreshing && Object.keys(statusMap).length === 0}
|
|
257
|
+
/>
|
|
258
|
+
</section>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default DashboardShell;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusBadge Component
|
|
3
|
+
* Industrial Command Center aesthetic - sharp corners, pulse dot, monospace
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { type OperationalStatus, STATUS_COLORS } from './types';
|
|
7
|
+
import { clsx } from 'clsx';
|
|
8
|
+
|
|
9
|
+
interface StatusBadgeProps {
|
|
10
|
+
status: OperationalStatus;
|
|
11
|
+
size?: 'sm' | 'md';
|
|
12
|
+
showLabel?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function StatusBadge({ status, size = 'md', showLabel = true }: StatusBadgeProps) {
|
|
16
|
+
const colors = STATUS_COLORS[status];
|
|
17
|
+
|
|
18
|
+
const sizeClasses = {
|
|
19
|
+
sm: 'px-1.5 py-0.5 text-[10px] gap-1',
|
|
20
|
+
md: 'px-2 py-1 text-xs gap-1.5',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const dotSizes = {
|
|
24
|
+
sm: 'w-1.5 h-1.5',
|
|
25
|
+
md: 'w-2 h-2',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<span
|
|
30
|
+
className={clsx(
|
|
31
|
+
// Base styles - sharp industrial corners
|
|
32
|
+
'inline-flex items-center rounded-sm font-mono font-semibold uppercase tracking-wider',
|
|
33
|
+
// Size
|
|
34
|
+
sizeClasses[size],
|
|
35
|
+
// Colours
|
|
36
|
+
colors.bg,
|
|
37
|
+
colors.text,
|
|
38
|
+
// Subtle glow effect
|
|
39
|
+
'shadow-sm',
|
|
40
|
+
colors.glow
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
{/* Pulse dot indicator */}
|
|
44
|
+
<span className="relative flex">
|
|
45
|
+
<span
|
|
46
|
+
className={clsx(
|
|
47
|
+
dotSizes[size],
|
|
48
|
+
'rounded-full',
|
|
49
|
+
status === 'RUN' ? 'bg-emerald-400' : status === 'WARN' ? 'bg-amber-400' : 'bg-rose-400'
|
|
50
|
+
)}
|
|
51
|
+
/>
|
|
52
|
+
{/* Pulse animation for RUN status */}
|
|
53
|
+
{status === 'RUN' && (
|
|
54
|
+
<span
|
|
55
|
+
className={clsx(
|
|
56
|
+
'absolute inset-0 rounded-full bg-emerald-400 animate-ping opacity-75',
|
|
57
|
+
dotSizes[size]
|
|
58
|
+
)}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
{/* Slower pulse for WARN status */}
|
|
62
|
+
{status === 'WARN' && (
|
|
63
|
+
<span
|
|
64
|
+
className={clsx(
|
|
65
|
+
'absolute inset-0 rounded-full bg-amber-400 animate-pulse opacity-50',
|
|
66
|
+
dotSizes[size]
|
|
67
|
+
)}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
</span>
|
|
71
|
+
|
|
72
|
+
{showLabel && <span>{status}</span>}
|
|
73
|
+
</span>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default StatusBadge;
|