@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.
- package/README.md +4 -7
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +206 -4
- package/package.json +1 -1
- package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
- package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
- package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
- package/templates/full/dashboard/src/components/reports/DigestStats.tsx +151 -0
- package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
- package/templates/full/dashboard/src/components/reports/HealthTrendsReport.tsx +192 -0
- package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
- package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -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/patterns/[id]/approve.ts +49 -0
- package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
- package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
- package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
- package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
- package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
- package/templates/full/dashboard/src/pages/feedback.astro +365 -0
- package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
- package/templates/full/dashboard/src/pages/map.astro +561 -0
- package/templates/full/dashboard/src/pages/revenue.astro +72 -0
- package/templates/full/dashboard/src/pages/tests.astro +431 -0
- package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
- package/templates/full/scripts/ops/verify-account-total.ts +256 -0
- package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
- package/templates/full/tests/integration/r2-archive.test.ts +108 -0
- package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
- package/templates/shared/.github/workflows/validate-controls.yml +27 -0
- package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
- package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
- package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
- package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
- package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
- package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
- package/templates/shared/dashboard/src/components/Toast.astro +170 -0
- package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
- package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
- package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
- package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
- package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
- package/templates/shared/dashboard/src/components/reports/ReportInfoButton.tsx +98 -0
- package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
- package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
- package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
- package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
- package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
- package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
- package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
- package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
- package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
- package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
- package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
- package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
- package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
- package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
- package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
- package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
- package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
- package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
- package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
- package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
- package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
- package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
- package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
- package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
- package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
- package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
- package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
- package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
- package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
- package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
- package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
- package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
- package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
- package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -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/scripts/ai-tab-controller.ts +419 -0
- package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
- package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
- package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
- package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
- package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
- package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
- package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
- package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
- package/templates/shared/dashboard/src/components/usage/transformers.ts +478 -0
- package/templates/shared/dashboard/src/components/usage/types.ts +283 -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/components/usage/usage-colors.ts +292 -0
- package/templates/shared/dashboard/src/lib/cloudflare/analytics.ts +310 -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/usage/ai-models.ts +235 -0
- package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
- package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
- package/templates/shared/tests/e2e/usage-api.test.ts +909 -0
- package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
- package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -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/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
- package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
- package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -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/analytics.astro +64 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
- package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
- package/templates/standard/tests/integration/connectors.test.ts +241 -0
- package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
- package/templates/standard/tests/integration/ingestion.test.ts +211 -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/dedup.test.ts +350 -0
- package/templates/standard/tests/unit/error-collector/github.test.ts +187 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UsageChart Component
|
|
3
|
+
* Industrial Command Center aesthetic - stacked bar/area chart with radar grid texture
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import {
|
|
8
|
+
AreaChart,
|
|
9
|
+
Area,
|
|
10
|
+
BarChart,
|
|
11
|
+
Bar,
|
|
12
|
+
XAxis,
|
|
13
|
+
YAxis,
|
|
14
|
+
CartesianGrid,
|
|
15
|
+
Tooltip,
|
|
16
|
+
Legend,
|
|
17
|
+
ResponsiveContainer,
|
|
18
|
+
type TooltipProps,
|
|
19
|
+
} from 'recharts';
|
|
20
|
+
import { format, parseISO } from 'date-fns';
|
|
21
|
+
import { type UsageRow, type ChartDataPoint, METRIC_COLORS } from './types';
|
|
22
|
+
import type { GranularResponse, ToolType } from '../unified/types';
|
|
23
|
+
import { TOOL_CONFIG } from '../unified/types';
|
|
24
|
+
|
|
25
|
+
interface UsageChartProps {
|
|
26
|
+
data: UsageRow[];
|
|
27
|
+
loading?: boolean;
|
|
28
|
+
period?: '24h' | '7d' | '30d';
|
|
29
|
+
/** When 'stacked', renders a BarChart with all 9 tools from granularData */
|
|
30
|
+
chartType?: 'area' | 'stacked';
|
|
31
|
+
/** Granular usage data for stacked bar chart (all 9 tools per day) */
|
|
32
|
+
granularData?: GranularResponse | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format large numbers with K/M suffix
|
|
37
|
+
*/
|
|
38
|
+
function formatNumber(value: number): string {
|
|
39
|
+
if (isNaN(value) || value === 0) return '0';
|
|
40
|
+
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
41
|
+
if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;
|
|
42
|
+
return value.toFixed(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Transform raw usage rows into chart data points
|
|
47
|
+
* Groups by time_bucket and aggregates metrics
|
|
48
|
+
*/
|
|
49
|
+
function transformData(rows: UsageRow[]): ChartDataPoint[] {
|
|
50
|
+
const grouped = new Map<string, ChartDataPoint>();
|
|
51
|
+
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
const existing = grouped.get(row.time_bucket);
|
|
54
|
+
if (existing) {
|
|
55
|
+
existing.d1 += (row.d1_reads || 0) + (row.d1_writes || 0);
|
|
56
|
+
existing.kv += (row.kv_reads || 0) + (row.kv_writes || 0);
|
|
57
|
+
existing.workers += row.workers_requests || 0;
|
|
58
|
+
existing.total = existing.d1 + existing.kv + existing.workers;
|
|
59
|
+
} else {
|
|
60
|
+
const d1 = (row.d1_reads || 0) + (row.d1_writes || 0);
|
|
61
|
+
const kv = (row.kv_reads || 0) + (row.kv_writes || 0);
|
|
62
|
+
const workers = row.workers_requests || 0;
|
|
63
|
+
grouped.set(row.time_bucket, {
|
|
64
|
+
time: row.time_bucket,
|
|
65
|
+
timestamp: new Date(row.time_bucket).getTime(),
|
|
66
|
+
d1,
|
|
67
|
+
kv,
|
|
68
|
+
workers,
|
|
69
|
+
total: d1 + kv + workers,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Array.from(grouped.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Custom tooltip with dark industrial styling
|
|
79
|
+
*/
|
|
80
|
+
function CustomTooltip({ active, payload, label }: TooltipProps<number, string>) {
|
|
81
|
+
if (!active || !payload || !payload.length) return null;
|
|
82
|
+
|
|
83
|
+
let formattedTime: string;
|
|
84
|
+
try {
|
|
85
|
+
formattedTime = format(parseISO(label), 'MMM d, HH:mm');
|
|
86
|
+
} catch {
|
|
87
|
+
formattedTime = label;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="bg-white dark:bg-slate-900 border border-gray-300 dark:border-slate-700 rounded-sm p-3 shadow-xl">
|
|
92
|
+
<p className="text-gray-600 dark:text-slate-400 text-xs font-mono mb-2">{formattedTime}</p>
|
|
93
|
+
<div className="space-y-1">
|
|
94
|
+
{payload.map((entry) => (
|
|
95
|
+
<div key={entry.name} className="flex items-center justify-between gap-4">
|
|
96
|
+
<span className="flex items-center gap-2">
|
|
97
|
+
<span className="w-2 h-2 rounded-sm" style={{ backgroundColor: entry.color }} />
|
|
98
|
+
<span className="text-gray-700 dark:text-slate-300 text-xs uppercase tracking-wide">
|
|
99
|
+
{entry.name}
|
|
100
|
+
</span>
|
|
101
|
+
</span>
|
|
102
|
+
<span className="text-gray-900 dark:text-slate-100 text-xs font-mono font-semibold">
|
|
103
|
+
{formatNumber(entry.value as number)}
|
|
104
|
+
</span>
|
|
105
|
+
</div>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Custom tooltip for stacked bar chart with all 9 tools
|
|
114
|
+
*/
|
|
115
|
+
function StackedTooltip({ active, payload, label }: TooltipProps<number, string>) {
|
|
116
|
+
if (!active || !payload || !payload.length) return null;
|
|
117
|
+
|
|
118
|
+
// Format date (YYYY-MM-DD format from granular API)
|
|
119
|
+
let formattedDate: string;
|
|
120
|
+
try {
|
|
121
|
+
formattedDate = format(parseISO(label), 'MMM d, yyyy');
|
|
122
|
+
} catch {
|
|
123
|
+
formattedDate = label;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Calculate total
|
|
127
|
+
const total = payload.reduce((sum, entry) => sum + ((entry.value as number) || 0), 0);
|
|
128
|
+
|
|
129
|
+
// Sort by value descending for display
|
|
130
|
+
const sortedPayload = [...payload].sort(
|
|
131
|
+
(a, b) => ((b.value as number) || 0) - ((a.value as number) || 0)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="bg-white dark:bg-slate-900 border border-gray-300 dark:border-slate-700 rounded-sm p-3 shadow-xl min-w-[180px]">
|
|
136
|
+
<p className="text-gray-600 dark:text-slate-400 text-xs font-mono mb-2 border-b border-gray-200 dark:border-slate-700 pb-2">
|
|
137
|
+
{formattedDate}
|
|
138
|
+
</p>
|
|
139
|
+
<div className="space-y-1">
|
|
140
|
+
{sortedPayload
|
|
141
|
+
.filter((entry) => (entry.value as number) > 0)
|
|
142
|
+
.map((entry) => (
|
|
143
|
+
<div key={entry.name} className="flex items-center justify-between gap-4">
|
|
144
|
+
<span className="flex items-center gap-2">
|
|
145
|
+
<span className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: entry.color }} />
|
|
146
|
+
<span className="text-gray-700 dark:text-slate-300 text-xs">{entry.name}</span>
|
|
147
|
+
</span>
|
|
148
|
+
<span className="text-gray-900 dark:text-slate-100 text-xs font-mono font-semibold">
|
|
149
|
+
{formatNumber(entry.value as number)}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
<div className="border-t border-gray-200 dark:border-slate-700 mt-2 pt-2 flex justify-between">
|
|
155
|
+
<span className="text-gray-600 dark:text-slate-400 text-xs font-semibold">Total</span>
|
|
156
|
+
<span className="text-gray-900 dark:text-slate-100 text-xs font-mono font-bold">
|
|
157
|
+
{formatNumber(total)}
|
|
158
|
+
</span>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Loading skeleton with industrial styling
|
|
166
|
+
*/
|
|
167
|
+
function ChartSkeleton() {
|
|
168
|
+
return (
|
|
169
|
+
<div className="h-64 bg-gray-50 dark:bg-slate-900/50 rounded-sm border border-gray-200 dark:border-slate-800 animate-pulse flex items-center justify-center">
|
|
170
|
+
<div className="text-gray-400 dark:text-slate-600 font-mono text-sm">
|
|
171
|
+
Loading activity data...
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function UsageChart({
|
|
178
|
+
data,
|
|
179
|
+
loading = false,
|
|
180
|
+
period = '24h',
|
|
181
|
+
chartType = 'area',
|
|
182
|
+
granularData,
|
|
183
|
+
}: UsageChartProps) {
|
|
184
|
+
const chartData = useMemo(() => transformData(data), [data]);
|
|
185
|
+
|
|
186
|
+
// Transform granular data for stacked bar chart (all 9 tools)
|
|
187
|
+
const stackedChartData = useMemo(() => {
|
|
188
|
+
if (!granularData?.byDate) return [];
|
|
189
|
+
return granularData.byDate.map((day) => ({
|
|
190
|
+
...day,
|
|
191
|
+
// Ensure all tool values exist for stacking
|
|
192
|
+
workers: day.workers || 0,
|
|
193
|
+
d1: day.d1 || 0,
|
|
194
|
+
kv: day.kv || 0,
|
|
195
|
+
r2: day.r2 || 0,
|
|
196
|
+
vectorize: day.vectorize || 0,
|
|
197
|
+
durableObjects: day.durableObjects || 0,
|
|
198
|
+
queues: day.queues || 0,
|
|
199
|
+
workersAI: day.workersAI || 0,
|
|
200
|
+
pages: day.pages || 0,
|
|
201
|
+
}));
|
|
202
|
+
}, [granularData]);
|
|
203
|
+
|
|
204
|
+
// Sorted tool keys by order from TOOL_CONFIG
|
|
205
|
+
const sortedToolKeys = useMemo(() => {
|
|
206
|
+
return (Object.keys(TOOL_CONFIG) as ToolType[]).sort(
|
|
207
|
+
(a, b) => TOOL_CONFIG[a].order - TOOL_CONFIG[b].order
|
|
208
|
+
);
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
// Format X-axis ticks based on period and chart type
|
|
212
|
+
const formatXAxisTick = (value: string) => {
|
|
213
|
+
try {
|
|
214
|
+
const date = parseISO(value);
|
|
215
|
+
// For stacked chart (daily data) or longer periods, show date (MMM d)
|
|
216
|
+
// For 24h area chart, show time (HH:mm)
|
|
217
|
+
if (chartType === 'stacked' || period !== '24h') {
|
|
218
|
+
return format(date, 'MMM d');
|
|
219
|
+
}
|
|
220
|
+
return format(date, 'HH:mm');
|
|
221
|
+
} catch {
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
if (loading) return <ChartSkeleton />;
|
|
227
|
+
|
|
228
|
+
// Stacked bar chart rendering for all 9 tools
|
|
229
|
+
if (chartType === 'stacked') {
|
|
230
|
+
if (stackedChartData.length === 0) {
|
|
231
|
+
return (
|
|
232
|
+
<div className="h-64 bg-gray-50 dark:bg-slate-900/50 rounded-sm border border-gray-200 dark:border-slate-800 flex items-center justify-center">
|
|
233
|
+
<div className="text-gray-500 dark:text-slate-500 font-mono text-sm">
|
|
234
|
+
No granular usage data available
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div className="h-64 bg-white dark:bg-slate-950 rounded-sm border border-gray-200 dark:border-slate-800 p-4 relative overflow-hidden">
|
|
242
|
+
{/* Subtle grid texture overlay for radar feel */}
|
|
243
|
+
<div
|
|
244
|
+
className="absolute inset-0 opacity-5 pointer-events-none"
|
|
245
|
+
style={{
|
|
246
|
+
backgroundImage: `
|
|
247
|
+
linear-gradient(to right, #475569 1px, transparent 1px),
|
|
248
|
+
linear-gradient(to bottom, #475569 1px, transparent 1px)
|
|
249
|
+
`,
|
|
250
|
+
backgroundSize: '20px 20px',
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
|
|
254
|
+
<div className="w-full h-full min-h-[200px]">
|
|
255
|
+
<ResponsiveContainer width="100%" height="100%" minWidth={0} minHeight={180}>
|
|
256
|
+
<BarChart data={stackedChartData} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}>
|
|
257
|
+
<CartesianGrid strokeDasharray="3 3" stroke="#334155" strokeOpacity={0.5} />
|
|
258
|
+
<XAxis
|
|
259
|
+
dataKey="date"
|
|
260
|
+
stroke="#64748b"
|
|
261
|
+
fontSize={10}
|
|
262
|
+
fontFamily="ui-monospace, monospace"
|
|
263
|
+
tickFormatter={formatXAxisTick}
|
|
264
|
+
tick={{ fill: '#64748b' }}
|
|
265
|
+
axisLine={{ stroke: '#475569' }}
|
|
266
|
+
tickLine={{ stroke: '#475569' }}
|
|
267
|
+
/>
|
|
268
|
+
<YAxis
|
|
269
|
+
stroke="#64748b"
|
|
270
|
+
fontSize={10}
|
|
271
|
+
fontFamily="ui-monospace, monospace"
|
|
272
|
+
tickFormatter={formatNumber}
|
|
273
|
+
tick={{ fill: '#64748b' }}
|
|
274
|
+
axisLine={{ stroke: '#475569' }}
|
|
275
|
+
tickLine={{ stroke: '#475569' }}
|
|
276
|
+
width={45}
|
|
277
|
+
/>
|
|
278
|
+
<Tooltip content={<StackedTooltip />} />
|
|
279
|
+
<Legend
|
|
280
|
+
wrapperStyle={{ fontSize: '10px', fontFamily: 'ui-monospace, monospace' }}
|
|
281
|
+
iconType="square"
|
|
282
|
+
iconSize={8}
|
|
283
|
+
/>
|
|
284
|
+
{sortedToolKeys.map((toolKey) => (
|
|
285
|
+
<Bar
|
|
286
|
+
key={toolKey}
|
|
287
|
+
dataKey={toolKey}
|
|
288
|
+
name={TOOL_CONFIG[toolKey].label}
|
|
289
|
+
stackId="usage"
|
|
290
|
+
fill={TOOL_CONFIG[toolKey].color}
|
|
291
|
+
/>
|
|
292
|
+
))}
|
|
293
|
+
</BarChart>
|
|
294
|
+
</ResponsiveContainer>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (chartData.length === 0) {
|
|
301
|
+
return (
|
|
302
|
+
<div className="h-64 bg-gray-50 dark:bg-slate-900/50 rounded-sm border border-gray-200 dark:border-slate-800 flex items-center justify-center">
|
|
303
|
+
<div className="text-gray-500 dark:text-slate-500 font-mono text-sm">
|
|
304
|
+
No activity data available
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<div className="h-64 bg-white dark:bg-slate-950 rounded-sm border border-gray-200 dark:border-slate-800 p-4 relative overflow-hidden">
|
|
312
|
+
{/* Subtle grid texture overlay for radar feel */}
|
|
313
|
+
<div
|
|
314
|
+
className="absolute inset-0 opacity-5 pointer-events-none"
|
|
315
|
+
style={{
|
|
316
|
+
backgroundImage: `
|
|
317
|
+
linear-gradient(to right, #475569 1px, transparent 1px),
|
|
318
|
+
linear-gradient(to bottom, #475569 1px, transparent 1px)
|
|
319
|
+
`,
|
|
320
|
+
backgroundSize: '20px 20px',
|
|
321
|
+
}}
|
|
322
|
+
/>
|
|
323
|
+
|
|
324
|
+
<div className="w-full h-full min-h-[200px]">
|
|
325
|
+
<ResponsiveContainer width="100%" height="100%" minWidth={0} minHeight={180}>
|
|
326
|
+
<AreaChart data={chartData} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}>
|
|
327
|
+
{/* Grid with industrial styling */}
|
|
328
|
+
<CartesianGrid strokeDasharray="3 3" stroke="#334155" strokeOpacity={0.5} />
|
|
329
|
+
|
|
330
|
+
{/* X-Axis - Time */}
|
|
331
|
+
<XAxis
|
|
332
|
+
dataKey="time"
|
|
333
|
+
stroke="#64748b"
|
|
334
|
+
fontSize={10}
|
|
335
|
+
fontFamily="ui-monospace, monospace"
|
|
336
|
+
tickFormatter={formatXAxisTick}
|
|
337
|
+
tick={{ fill: '#64748b' }}
|
|
338
|
+
axisLine={{ stroke: '#475569' }}
|
|
339
|
+
tickLine={{ stroke: '#475569' }}
|
|
340
|
+
/>
|
|
341
|
+
|
|
342
|
+
{/* Y-Axis - Count */}
|
|
343
|
+
<YAxis
|
|
344
|
+
stroke="#64748b"
|
|
345
|
+
fontSize={10}
|
|
346
|
+
fontFamily="ui-monospace, monospace"
|
|
347
|
+
tickFormatter={formatNumber}
|
|
348
|
+
tick={{ fill: '#64748b' }}
|
|
349
|
+
axisLine={{ stroke: '#475569' }}
|
|
350
|
+
tickLine={{ stroke: '#475569' }}
|
|
351
|
+
width={45}
|
|
352
|
+
/>
|
|
353
|
+
|
|
354
|
+
<Tooltip content={<CustomTooltip />} />
|
|
355
|
+
|
|
356
|
+
{/* Stacked areas - Workers (bottom), KV (middle), D1 (top) */}
|
|
357
|
+
<Area
|
|
358
|
+
type="monotone"
|
|
359
|
+
dataKey="workers"
|
|
360
|
+
name="Workers"
|
|
361
|
+
stackId="1"
|
|
362
|
+
stroke={METRIC_COLORS.workers.stroke}
|
|
363
|
+
fill={METRIC_COLORS.workers.fill}
|
|
364
|
+
strokeWidth={2}
|
|
365
|
+
/>
|
|
366
|
+
<Area
|
|
367
|
+
type="monotone"
|
|
368
|
+
dataKey="kv"
|
|
369
|
+
name="KV"
|
|
370
|
+
stackId="1"
|
|
371
|
+
stroke={METRIC_COLORS.kv.stroke}
|
|
372
|
+
fill={METRIC_COLORS.kv.fill}
|
|
373
|
+
strokeWidth={2}
|
|
374
|
+
/>
|
|
375
|
+
<Area
|
|
376
|
+
type="monotone"
|
|
377
|
+
dataKey="d1"
|
|
378
|
+
name="D1"
|
|
379
|
+
stackId="1"
|
|
380
|
+
stroke={METRIC_COLORS.d1.stroke}
|
|
381
|
+
fill={METRIC_COLORS.d1.fill}
|
|
382
|
+
strokeWidth={2}
|
|
383
|
+
/>
|
|
384
|
+
</AreaChart>
|
|
385
|
+
</ResponsiveContainer>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export default UsageChart;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Usage Dashboard - React Components
|
|
3
|
+
* Industrial Command Center aesthetic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export type {
|
|
8
|
+
OperationalStatus,
|
|
9
|
+
Period,
|
|
10
|
+
CircuitBreakerState,
|
|
11
|
+
ProjectStatus,
|
|
12
|
+
UsageRow,
|
|
13
|
+
ChartDataPoint,
|
|
14
|
+
ProjectTableRow,
|
|
15
|
+
StatusResponse,
|
|
16
|
+
QueryResponse,
|
|
17
|
+
MetricType,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export { METRIC_COLORS, STATUS_COLORS, CB_COLORS } from './types';
|
|
21
|
+
|
|
22
|
+
// Components
|
|
23
|
+
export { StatusBadge } from './StatusBadge';
|
|
24
|
+
export { UsageChart } from './UsageChart';
|
|
25
|
+
export { DashboardShell } from './DashboardShell';
|
|
26
|
+
|
|
27
|
+
{{#if isStandard}}
|
|
28
|
+
// Standard tier components
|
|
29
|
+
export { UsageTable } from './UsageTable';
|
|
30
|
+
{{/if}}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Usage Dashboard Types
|
|
3
|
+
* Industrial Command Center aesthetic - data-dense, monospace metrics
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type OperationalStatus = 'RUN' | 'WARN' | 'STOP';
|
|
7
|
+
export type Period = '24h' | '7d';
|
|
8
|
+
export type CircuitBreakerState = 'active' | 'tripped';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Project status summary for table display
|
|
12
|
+
*/
|
|
13
|
+
export interface ProjectStatus {
|
|
14
|
+
status: OperationalStatus;
|
|
15
|
+
spend: number;
|
|
16
|
+
cap: number;
|
|
17
|
+
percentage: number;
|
|
18
|
+
circuitBreaker: CircuitBreakerState;
|
|
19
|
+
lastSeen?: string; // ISO timestamp from heartbeat
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Raw usage row from the API - time-series data
|
|
24
|
+
*/
|
|
25
|
+
export interface UsageRow {
|
|
26
|
+
time_bucket: string;
|
|
27
|
+
project_id: string;
|
|
28
|
+
d1_writes: number;
|
|
29
|
+
d1_reads: number;
|
|
30
|
+
kv_reads: number;
|
|
31
|
+
kv_writes: number;
|
|
32
|
+
workers_requests: number;
|
|
33
|
+
interaction_count: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Aggregated data point for chart display
|
|
38
|
+
*/
|
|
39
|
+
export interface ChartDataPoint {
|
|
40
|
+
time: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
d1: number;
|
|
43
|
+
kv: number;
|
|
44
|
+
workers: number;
|
|
45
|
+
total: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Table row with project summary
|
|
50
|
+
*/
|
|
51
|
+
export interface ProjectTableRow {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
status: OperationalStatus;
|
|
55
|
+
activity: number;
|
|
56
|
+
activityTrend: number[]; // Last 6 data points for sparkline
|
|
57
|
+
spend: number;
|
|
58
|
+
cap: number;
|
|
59
|
+
percentage: number;
|
|
60
|
+
circuitBreaker: CircuitBreakerState;
|
|
61
|
+
lastSeen?: string; // ISO timestamp from heartbeat
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* API response: project status endpoint
|
|
66
|
+
*/
|
|
67
|
+
export interface StatusResponse {
|
|
68
|
+
success: true;
|
|
69
|
+
projects: Record<string, ProjectStatus>;
|
|
70
|
+
timestamp: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* API response: usage query endpoint
|
|
75
|
+
*/
|
|
76
|
+
export interface QueryResponse {
|
|
77
|
+
success: true;
|
|
78
|
+
data: UsageRow[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Metric type for chart colour mapping
|
|
83
|
+
*/
|
|
84
|
+
export type MetricType = 'd1' | 'kv' | 'workers';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Chart colour configuration
|
|
88
|
+
*/
|
|
89
|
+
export const METRIC_COLORS: Record<MetricType, { fill: string; stroke: string }> = {
|
|
90
|
+
d1: { fill: 'rgba(16, 185, 129, 0.3)', stroke: '#10b981' }, // emerald
|
|
91
|
+
kv: { fill: 'rgba(245, 158, 11, 0.3)', stroke: '#f59e0b' }, // amber
|
|
92
|
+
workers: { fill: 'rgba(59, 130, 246, 0.3)', stroke: '#3b82f6' }, // blue
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Status colour configuration
|
|
97
|
+
*/
|
|
98
|
+
export const STATUS_COLORS: Record<OperationalStatus, { bg: string; text: string; glow: string }> =
|
|
99
|
+
{
|
|
100
|
+
RUN: {
|
|
101
|
+
bg: 'bg-emerald-500/20',
|
|
102
|
+
text: 'text-emerald-400',
|
|
103
|
+
glow: 'shadow-emerald-500/20',
|
|
104
|
+
},
|
|
105
|
+
WARN: {
|
|
106
|
+
bg: 'bg-amber-500/20',
|
|
107
|
+
text: 'text-amber-400',
|
|
108
|
+
glow: 'shadow-amber-500/20',
|
|
109
|
+
},
|
|
110
|
+
STOP: {
|
|
111
|
+
bg: 'bg-rose-500/20',
|
|
112
|
+
text: 'text-rose-400',
|
|
113
|
+
glow: 'shadow-rose-500/20',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Circuit breaker colour configuration
|
|
119
|
+
*/
|
|
120
|
+
export const CB_COLORS: Record<CircuitBreakerState, string> = {
|
|
121
|
+
active: 'bg-emerald-500',
|
|
122
|
+
tripped: 'bg-rose-500',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Health status based on heartbeat recency
|
|
127
|
+
*/
|
|
128
|
+
export type HealthStatus = 'online' | 'idle' | 'unknown';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Health indicator colour configuration
|
|
132
|
+
*/
|
|
133
|
+
export const HEALTH_COLORS: Record<HealthStatus, { dot: string; text: string }> = {
|
|
134
|
+
online: { dot: '🟢', text: 'text-emerald-400' },
|
|
135
|
+
idle: { dot: '🟡', text: 'text-amber-400' },
|
|
136
|
+
unknown: { dot: '⚪', text: 'text-slate-500' },
|
|
137
|
+
};
|