@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,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Dashboard Shared Types
|
|
3
|
+
*
|
|
4
|
+
* Centralised type definitions for usage dashboard components.
|
|
5
|
+
* Part of task-17 (Enhanced Cloudflare Usage Dashboard).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ResourceType =
|
|
9
|
+
| 'worker'
|
|
10
|
+
| 'd1'
|
|
11
|
+
| 'kv'
|
|
12
|
+
| 'r2'
|
|
13
|
+
| 'vectorize'
|
|
14
|
+
| 'pages'
|
|
15
|
+
| 'queues'
|
|
16
|
+
| 'workflows'
|
|
17
|
+
| 'do'
|
|
18
|
+
| 'ai-gateway';
|
|
19
|
+
|
|
20
|
+
export type ResourceStatus = 'healthy' | 'warning' | 'high' | 'critical';
|
|
21
|
+
|
|
22
|
+
export type SortDirection = 'asc' | 'desc';
|
|
23
|
+
|
|
24
|
+
export type CompareMode = 'none' | 'lastMonth' | 'custom';
|
|
25
|
+
|
|
26
|
+
export interface UnifiedResource {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
type: ResourceType;
|
|
30
|
+
project: string;
|
|
31
|
+
/** GitHub repository URL for the project (if available) */
|
|
32
|
+
repoUrl?: string | null;
|
|
33
|
+
usage: {
|
|
34
|
+
value: number;
|
|
35
|
+
unit: string;
|
|
36
|
+
formatted: string;
|
|
37
|
+
};
|
|
38
|
+
/** Percentage of monthly limit used (0-100+) */
|
|
39
|
+
limitPct?: number | null;
|
|
40
|
+
costCurrent: number;
|
|
41
|
+
costPrior: number;
|
|
42
|
+
costDelta: number;
|
|
43
|
+
costDeltaPct: number | 'NEW' | null;
|
|
44
|
+
status: ResourceStatus;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SparklinePoint {
|
|
48
|
+
date: string;
|
|
49
|
+
value: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DateRange {
|
|
53
|
+
startDate: string;
|
|
54
|
+
endDate: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface FilterState {
|
|
58
|
+
period: '24h' | '7d' | '30d';
|
|
59
|
+
project: 'all' | 'brand-copilot' | 'scout' | 'platform';
|
|
60
|
+
compareMode: CompareMode;
|
|
61
|
+
customDateRange?: DateRange;
|
|
62
|
+
serviceTypes: ResourceType[];
|
|
63
|
+
searchQuery: string;
|
|
64
|
+
onlyChanged: boolean;
|
|
65
|
+
nonZeroCost: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SortState {
|
|
69
|
+
column: string;
|
|
70
|
+
direction: SortDirection;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Resource type metadata
|
|
74
|
+
export const RESOURCE_TYPE_ICONS: Record<ResourceType, string> = {
|
|
75
|
+
worker: '⚡',
|
|
76
|
+
d1: '🗄️',
|
|
77
|
+
kv: '🔑',
|
|
78
|
+
r2: '📦',
|
|
79
|
+
vectorize: '🧮',
|
|
80
|
+
pages: '📄',
|
|
81
|
+
queues: '📬',
|
|
82
|
+
workflows: '🔄',
|
|
83
|
+
do: '🔗',
|
|
84
|
+
'ai-gateway': '🤖',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {
|
|
88
|
+
worker: 'Worker',
|
|
89
|
+
d1: 'D1',
|
|
90
|
+
kv: 'KV',
|
|
91
|
+
r2: 'R2',
|
|
92
|
+
vectorize: 'Vectorize',
|
|
93
|
+
pages: 'Pages',
|
|
94
|
+
queues: 'Queues',
|
|
95
|
+
workflows: 'Workflows',
|
|
96
|
+
do: 'Durable Objects',
|
|
97
|
+
'ai-gateway': 'AI Gateway',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const STATUS_COLOURS: Record<ResourceStatus, string> = {
|
|
101
|
+
healthy: '#10B981',
|
|
102
|
+
warning: '#F59E0B',
|
|
103
|
+
high: '#F97316',
|
|
104
|
+
critical: '#EF4444',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Utility functions
|
|
108
|
+
export function formatCurrency(amount: number): string {
|
|
109
|
+
if (amount === 0) return '$0.00';
|
|
110
|
+
if (amount < 0.01) return '< $0.01';
|
|
111
|
+
return `$${amount.toFixed(2)}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function formatDeltaPct(pct: number | 'NEW' | null): string {
|
|
115
|
+
if (pct === 'NEW') return 'NEW';
|
|
116
|
+
if (pct === null) return '—';
|
|
117
|
+
if (pct === 0) return '0%';
|
|
118
|
+
const sign = pct > 0 ? '+' : '';
|
|
119
|
+
return `${sign}${pct.toFixed(1)}%`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getDeltaClass(pct: number | 'NEW' | null): string {
|
|
123
|
+
if (pct === 'NEW') return 'delta-new';
|
|
124
|
+
if (pct === null || pct === 0) return 'delta-neutral';
|
|
125
|
+
return pct > 0 ? 'delta-up' : 'delta-down';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function formatLimitPct(pct: number | null | undefined): string {
|
|
129
|
+
if (pct === null || pct === undefined) return '—';
|
|
130
|
+
if (pct === 0) return '0%';
|
|
131
|
+
if (pct >= 100) return `${pct.toFixed(0)}%`;
|
|
132
|
+
if (pct >= 10) return `${pct.toFixed(0)}%`;
|
|
133
|
+
return `${pct.toFixed(1)}%`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getLimitPctClass(pct: number | null | undefined): string {
|
|
137
|
+
if (pct === null || pct === undefined) return 'limit-neutral';
|
|
138
|
+
if (pct >= 90) return 'limit-critical';
|
|
139
|
+
if (pct >= 75) return 'limit-high';
|
|
140
|
+
if (pct >= 50) return 'limit-warning';
|
|
141
|
+
return 'limit-ok';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Usage Overview Types (task-usage-redesign)
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Provider type for usage metrics
|
|
150
|
+
*/
|
|
151
|
+
export type UsageProvider = 'cloudflare' | 'github';
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Utilization status based on percentage thresholds
|
|
155
|
+
*/
|
|
156
|
+
export type UtilizationStatus = 'ok' | 'warning' | 'critical' | 'overage';
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Normalized resource metric for cross-provider comparison
|
|
160
|
+
*/
|
|
161
|
+
export interface ResourceMetric {
|
|
162
|
+
id: string;
|
|
163
|
+
label: string;
|
|
164
|
+
provider: UsageProvider;
|
|
165
|
+
current: number;
|
|
166
|
+
limit: number | null; // null if unlimited
|
|
167
|
+
unit: string;
|
|
168
|
+
percentage: number;
|
|
169
|
+
costEstimate: number;
|
|
170
|
+
status: UtilizationStatus;
|
|
171
|
+
overage: number; // Amount over limit (0 if under)
|
|
172
|
+
overageCost: number; // Cost of overage
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Per-project usage breakdown with service-level detail
|
|
177
|
+
*/
|
|
178
|
+
export interface ProjectServiceBreakdown {
|
|
179
|
+
projectId: string;
|
|
180
|
+
projectName: string;
|
|
181
|
+
services: ResourceMetric[];
|
|
182
|
+
totalCost: number;
|
|
183
|
+
totalUtilization: number; // Weighted average or max
|
|
184
|
+
circuitBreakerStatus: 'active' | 'inactive' | 'triggered';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Global health summary for a provider
|
|
189
|
+
*/
|
|
190
|
+
export interface ProviderHealth {
|
|
191
|
+
provider: UsageProvider;
|
|
192
|
+
percentage: number;
|
|
193
|
+
warnings: number; // Count of services in warning/critical status
|
|
194
|
+
status: UtilizationStatus;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* GitHub repository usage breakdown
|
|
199
|
+
*/
|
|
200
|
+
export interface RepoUsage {
|
|
201
|
+
repoName: string;
|
|
202
|
+
actionsMinutes: number;
|
|
203
|
+
actionsMinutesPct: number;
|
|
204
|
+
storageBytes: number;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Complete dashboard data response
|
|
209
|
+
*/
|
|
210
|
+
export interface UsageOverviewData {
|
|
211
|
+
timestamp: string;
|
|
212
|
+
period: {
|
|
213
|
+
start: string;
|
|
214
|
+
end: string;
|
|
215
|
+
};
|
|
216
|
+
health: {
|
|
217
|
+
cloudflare: ProviderHealth;
|
|
218
|
+
github: ProviderHealth;
|
|
219
|
+
};
|
|
220
|
+
burnRate: {
|
|
221
|
+
mtdCost: number;
|
|
222
|
+
projectedMonthlyCost: number;
|
|
223
|
+
dailyBurnRate: number;
|
|
224
|
+
vsLastMonthPct: number;
|
|
225
|
+
budget: number | null;
|
|
226
|
+
};
|
|
227
|
+
cloudflareServices: ResourceMetric[];
|
|
228
|
+
githubServices: ResourceMetric[];
|
|
229
|
+
projectBreakdowns: ProjectServiceBreakdown[];
|
|
230
|
+
githubRepos: RepoUsage[];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Calculate utilization status from percentage
|
|
235
|
+
*/
|
|
236
|
+
export function getUtilizationStatus(percentage: number): UtilizationStatus {
|
|
237
|
+
if (percentage >= 100) return 'overage';
|
|
238
|
+
if (percentage >= 90) return 'critical';
|
|
239
|
+
if (percentage >= 70) return 'warning';
|
|
240
|
+
return 'ok';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Status colours for utilization bars
|
|
245
|
+
*/
|
|
246
|
+
export const UTILIZATION_COLOURS: Record<UtilizationStatus, string> = {
|
|
247
|
+
ok: '#22c55e', // green-500
|
|
248
|
+
warning: '#eab308', // yellow-500
|
|
249
|
+
critical: '#ef4444', // red-500
|
|
250
|
+
overage: '#dc2626', // red-600
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Format large numbers with K/M/B suffixes
|
|
255
|
+
*/
|
|
256
|
+
export function formatLargeNumber(num: number): string {
|
|
257
|
+
if (num >= 1_000_000_000) {
|
|
258
|
+
return `${(num / 1_000_000_000).toFixed(1)}B`;
|
|
259
|
+
}
|
|
260
|
+
if (num >= 1_000_000) {
|
|
261
|
+
return `${(num / 1_000_000).toFixed(1)}M`;
|
|
262
|
+
}
|
|
263
|
+
if (num >= 1_000) {
|
|
264
|
+
return `${(num / 1_000).toFixed(1)}K`;
|
|
265
|
+
}
|
|
266
|
+
return num.toFixed(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Format bytes to human readable (GB, MB, etc.)
|
|
271
|
+
*/
|
|
272
|
+
export function formatBytes(bytes: number): string {
|
|
273
|
+
if (bytes >= 1_000_000_000) {
|
|
274
|
+
return `${(bytes / 1_000_000_000).toFixed(2)} GB`;
|
|
275
|
+
}
|
|
276
|
+
if (bytes >= 1_000_000) {
|
|
277
|
+
return `${(bytes / 1_000_000).toFixed(2)} MB`;
|
|
278
|
+
}
|
|
279
|
+
if (bytes >= 1_000) {
|
|
280
|
+
return `${(bytes / 1_000).toFixed(2)} KB`;
|
|
281
|
+
}
|
|
282
|
+
return `${bytes} B`;
|
|
283
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertBanner Component
|
|
3
|
+
*
|
|
4
|
+
* Displays critical alerts for services approaching or exceeding limits.
|
|
5
|
+
* Dismissible alerts are stored in localStorage.
|
|
6
|
+
* Supports collapse/expand for managing alert visibility.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState, useMemo } from 'react';
|
|
10
|
+
import { AlertTriangle, X, ChevronDown, ChevronUp, ChevronsUpDown } from 'lucide-react';
|
|
11
|
+
import { clsx } from 'clsx';
|
|
12
|
+
import type { ServiceUtilisation, BurnRateData } from './types';
|
|
13
|
+
|
|
14
|
+
interface AlertBannerProps {
|
|
15
|
+
services: ServiceUtilisation[];
|
|
16
|
+
burnRate: BurnRateData | null;
|
|
17
|
+
onDismiss: (alertId: string) => void;
|
|
18
|
+
dismissedAlerts: Set<string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface AlertItem {
|
|
22
|
+
id: string;
|
|
23
|
+
type: 'budget' | 'service';
|
|
24
|
+
title: string;
|
|
25
|
+
detail: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function AlertBanner({ services, burnRate, onDismiss, dismissedAlerts }: AlertBannerProps) {
|
|
29
|
+
// Collapsed state - which alerts are collapsed (show title only)
|
|
30
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
|
|
31
|
+
// All alerts collapsed state for the header toggle
|
|
32
|
+
const [allCollapsed, setAllCollapsed] = useState(false);
|
|
33
|
+
|
|
34
|
+
// Filter to critical/overage alerts that haven't been dismissed
|
|
35
|
+
const criticalAlerts = services
|
|
36
|
+
.filter((s) => s.status === 'critical' || s.status === 'overage')
|
|
37
|
+
.filter((s) => !dismissedAlerts.has(s.id));
|
|
38
|
+
|
|
39
|
+
// Check for budget alert
|
|
40
|
+
const showBudgetAlert = burnRate?.status === 'red' && !dismissedAlerts.has('budget');
|
|
41
|
+
|
|
42
|
+
// Build unified alert list for count
|
|
43
|
+
const alerts = useMemo(() => {
|
|
44
|
+
const list: AlertItem[] = [];
|
|
45
|
+
|
|
46
|
+
if (showBudgetAlert && burnRate) {
|
|
47
|
+
list.push({
|
|
48
|
+
id: 'budget',
|
|
49
|
+
type: 'budget',
|
|
50
|
+
title: 'BUDGET ALERT: Projected spend exceeds target',
|
|
51
|
+
detail: `Projected: $${burnRate.projectedMonthlyCost.toFixed(2)} | Daily burn: $${burnRate.dailyBurnRate.toFixed(2)}/day | ${burnRate.daysRemaining} days remaining`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const alert of criticalAlerts) {
|
|
56
|
+
list.push({
|
|
57
|
+
id: alert.id,
|
|
58
|
+
type: 'service',
|
|
59
|
+
title: `CRITICAL: ${alert.label} at ${alert.percentage.toFixed(0)}% of limit`,
|
|
60
|
+
detail: `Current: ${alert.current.toLocaleString()} ${alert.unit} / Limit: ${alert.limit.toLocaleString()} ${alert.unit}${alert.costEstimate > 0 ? ` | Est. cost: $${alert.costEstimate.toFixed(2)}` : ''}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return list;
|
|
65
|
+
}, [showBudgetAlert, burnRate, criticalAlerts]);
|
|
66
|
+
|
|
67
|
+
if (alerts.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const toggleCollapse = (id: string) => {
|
|
72
|
+
setCollapsed((prev) => {
|
|
73
|
+
const next = new Set(prev);
|
|
74
|
+
if (next.has(id)) {
|
|
75
|
+
next.delete(id);
|
|
76
|
+
} else {
|
|
77
|
+
next.add(id);
|
|
78
|
+
}
|
|
79
|
+
return next;
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const toggleAll = () => {
|
|
84
|
+
if (allCollapsed) {
|
|
85
|
+
// Expand all
|
|
86
|
+
setCollapsed(new Set());
|
|
87
|
+
setAllCollapsed(false);
|
|
88
|
+
} else {
|
|
89
|
+
// Collapse all
|
|
90
|
+
setCollapsed(new Set(alerts.map((a) => a.id)));
|
|
91
|
+
setAllCollapsed(true);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="mb-6">
|
|
97
|
+
{/* Alert Header with count and collapse toggle */}
|
|
98
|
+
<div className="flex items-center justify-between mb-2">
|
|
99
|
+
<div className="flex items-center gap-2">
|
|
100
|
+
<AlertTriangle className="w-4 h-4 text-rose-400" />
|
|
101
|
+
<span className="text-xs font-semibold text-rose-300 uppercase tracking-wider">
|
|
102
|
+
{alerts.length} Active Alert{alerts.length !== 1 ? 's' : ''}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
onClick={toggleAll}
|
|
108
|
+
className="flex items-center gap-1 text-xs text-gray-600 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-300 transition-colors px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-slate-800"
|
|
109
|
+
>
|
|
110
|
+
<ChevronsUpDown className="w-3 h-3" />
|
|
111
|
+
{allCollapsed ? 'Expand All' : 'Collapse All'}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Alerts List */}
|
|
116
|
+
<div className="space-y-2">
|
|
117
|
+
{alerts.map((alert) => {
|
|
118
|
+
const isCollapsed = collapsed.has(alert.id);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
key={alert.id}
|
|
123
|
+
className="bg-rose-500/10 border border-rose-500/30 rounded-sm overflow-hidden transition-all"
|
|
124
|
+
>
|
|
125
|
+
{/* Alert Header Row - always visible */}
|
|
126
|
+
<div className="p-3 flex items-center gap-3">
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
onClick={() => toggleCollapse(alert.id)}
|
|
130
|
+
className="text-rose-400 hover:text-rose-300 p-1 rounded hover:bg-rose-500/20 transition-colors"
|
|
131
|
+
aria-label={isCollapsed ? 'Expand alert' : 'Collapse alert'}
|
|
132
|
+
>
|
|
133
|
+
{isCollapsed ? (
|
|
134
|
+
<ChevronDown className="w-4 h-4" />
|
|
135
|
+
) : (
|
|
136
|
+
<ChevronUp className="w-4 h-4" />
|
|
137
|
+
)}
|
|
138
|
+
</button>
|
|
139
|
+
<AlertTriangle className="w-4 h-4 text-rose-400 flex-shrink-0" />
|
|
140
|
+
<span className="text-rose-200 font-semibold text-sm flex-1 truncate">
|
|
141
|
+
{alert.title}
|
|
142
|
+
</span>
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
onClick={() => onDismiss(alert.id)}
|
|
146
|
+
className="text-rose-400 hover:text-rose-300 p-1 rounded hover:bg-rose-500/20 transition-colors"
|
|
147
|
+
aria-label="Dismiss alert"
|
|
148
|
+
>
|
|
149
|
+
<X className="w-4 h-4" />
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Alert Details - collapsible */}
|
|
154
|
+
<div
|
|
155
|
+
className={clsx(
|
|
156
|
+
'overflow-hidden transition-all duration-200 ease-in-out',
|
|
157
|
+
isCollapsed ? 'max-h-0' : 'max-h-24'
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
<div className="px-4 pb-3 pl-12">
|
|
161
|
+
<p className="text-rose-300/80 text-xs font-mono">{alert.detail}</p>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
})}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export default AlertBanner;
|