@littlebearapps/platform-admin-sdk 2.1.0 → 2.3.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 -5
- package/dist/check-upgrade.d.ts +29 -0
- package/dist/check-upgrade.js +97 -0
- package/dist/index.js +59 -4
- package/dist/manifest.d.ts +2 -0
- package/dist/scaffold.js +5 -1
- package/dist/templates.d.ts +6 -1
- package/dist/templates.js +141 -3
- package/dist/upgrade.d.ts +1 -0
- package/dist/upgrade.js +21 -2
- 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/FeatureUsageReport.tsx +339 -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/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/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/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/types.ts +283 -0
- package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -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-export.test.ts +784 -0
- package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -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/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
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* OverageCostCard - Displays base cost + overage breakdown
|
|
4
|
+
*
|
|
5
|
+
* Shows "$5.00 base + $0.00 overage" breakdown for Workers Paid Plan.
|
|
6
|
+
* Supports prorated base cost for sub-monthly periods.
|
|
7
|
+
*
|
|
8
|
+
* @created 2026-01-22
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/** Card title */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** ID for JavaScript updates */
|
|
15
|
+
id?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { title = 'Estimated Cost', id = 'overage-cost' } = Astro.props;
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
id={id}
|
|
23
|
+
class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 shadow-sm"
|
|
24
|
+
>
|
|
25
|
+
<!-- Header -->
|
|
26
|
+
<div class="flex items-center justify-between mb-4">
|
|
27
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">{title}</h3>
|
|
28
|
+
<span
|
|
29
|
+
id={`${id}-badge`}
|
|
30
|
+
class="px-2 py-0.5 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
|
31
|
+
>
|
|
32
|
+
Within Budget
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Total Cost Display -->
|
|
37
|
+
<div class="text-center mb-4">
|
|
38
|
+
<span id={`${id}-total`} class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
|
39
|
+
$--.--
|
|
40
|
+
</span>
|
|
41
|
+
<span id={`${id}-period-label`} class="text-sm text-gray-500 dark:text-gray-400 ml-1">
|
|
42
|
+
/ month
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Cost Breakdown -->
|
|
47
|
+
<div class="space-y-2 border-t border-gray-200 dark:border-gray-700 pt-3">
|
|
48
|
+
<!-- Base Cost -->
|
|
49
|
+
<div class="flex items-center justify-between">
|
|
50
|
+
<div class="flex items-center gap-2">
|
|
51
|
+
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
|
|
52
|
+
<span class="text-sm text-gray-600 dark:text-gray-300">Base Plan</span>
|
|
53
|
+
</div>
|
|
54
|
+
<span id={`${id}-base`} class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
55
|
+
$5.00
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- Overage Cost -->
|
|
60
|
+
<div class="flex items-center justify-between">
|
|
61
|
+
<div class="flex items-center gap-2">
|
|
62
|
+
<div id={`${id}-overage-dot`} class="w-2 h-2 rounded-full bg-green-500"></div>
|
|
63
|
+
<span class="text-sm text-gray-600 dark:text-gray-300">Overage</span>
|
|
64
|
+
</div>
|
|
65
|
+
<span id={`${id}-overage`} class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
66
|
+
$0.00
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Progress Bar (overage proportion) -->
|
|
72
|
+
<div class="mt-4">
|
|
73
|
+
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mb-1">
|
|
74
|
+
<span>Base</span>
|
|
75
|
+
<span id={`${id}-overage-pct`}>0% overage</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
78
|
+
<div class="h-full flex">
|
|
79
|
+
<div
|
|
80
|
+
id={`${id}-base-bar`}
|
|
81
|
+
class="bg-blue-500 transition-all duration-500"
|
|
82
|
+
style="width: 100%"
|
|
83
|
+
>
|
|
84
|
+
</div>
|
|
85
|
+
<div
|
|
86
|
+
id={`${id}-overage-bar`}
|
|
87
|
+
class="bg-orange-500 transition-all duration-500"
|
|
88
|
+
style="width: 0%"
|
|
89
|
+
>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Footnote -->
|
|
96
|
+
<p id={`${id}-note`} class="text-xs text-gray-400 dark:text-gray-500 mt-3 text-center">
|
|
97
|
+
Workers Paid Plan (prorated)
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<script>
|
|
102
|
+
interface OverageCostData {
|
|
103
|
+
baseCost: number;
|
|
104
|
+
overageCost: number;
|
|
105
|
+
totalCost: number;
|
|
106
|
+
periodLabel?: string; // "month" | "MTD" | "24h" etc.
|
|
107
|
+
note?: string;
|
|
108
|
+
isProrated?: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatCurrency(amount: number): string {
|
|
112
|
+
return `$${amount.toFixed(2)}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getBadgeClasses(overageCost: number, baseCost: number): string {
|
|
116
|
+
const overagePct = baseCost > 0 ? (overageCost / baseCost) * 100 : 0;
|
|
117
|
+
if (overagePct >= 100) {
|
|
118
|
+
return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400';
|
|
119
|
+
}
|
|
120
|
+
if (overagePct >= 50) {
|
|
121
|
+
return 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400';
|
|
122
|
+
}
|
|
123
|
+
if (overagePct >= 20) {
|
|
124
|
+
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400';
|
|
125
|
+
}
|
|
126
|
+
return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getBadgeText(overageCost: number, baseCost: number): string {
|
|
130
|
+
const overagePct = baseCost > 0 ? (overageCost / baseCost) * 100 : 0;
|
|
131
|
+
if (overagePct >= 100) return 'High Overage';
|
|
132
|
+
if (overagePct >= 50) return 'Moderate Overage';
|
|
133
|
+
if (overagePct >= 20) return 'Low Overage';
|
|
134
|
+
if (overageCost > 0) return 'Minimal Overage';
|
|
135
|
+
return 'Within Budget';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getOverageDotColor(overageCost: number): string {
|
|
139
|
+
if (overageCost <= 0) return 'bg-green-500';
|
|
140
|
+
if (overageCost < 1) return 'bg-yellow-500';
|
|
141
|
+
if (overageCost < 5) return 'bg-orange-500';
|
|
142
|
+
return 'bg-red-500';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function updateOverageCostCard(cardId: string, data: OverageCostData) {
|
|
146
|
+
const totalEl = document.getElementById(`${cardId}-total`);
|
|
147
|
+
const periodLabelEl = document.getElementById(`${cardId}-period-label`);
|
|
148
|
+
const baseEl = document.getElementById(`${cardId}-base`);
|
|
149
|
+
const overageEl = document.getElementById(`${cardId}-overage`);
|
|
150
|
+
const badgeEl = document.getElementById(`${cardId}-badge`);
|
|
151
|
+
const overagePctEl = document.getElementById(`${cardId}-overage-pct`);
|
|
152
|
+
const baseBarEl = document.getElementById(`${cardId}-base-bar`);
|
|
153
|
+
const overageBarEl = document.getElementById(`${cardId}-overage-bar`);
|
|
154
|
+
const overageDotEl = document.getElementById(`${cardId}-overage-dot`);
|
|
155
|
+
const noteEl = document.getElementById(`${cardId}-note`);
|
|
156
|
+
|
|
157
|
+
// Update total
|
|
158
|
+
if (totalEl) {
|
|
159
|
+
totalEl.textContent = formatCurrency(data.totalCost);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Update period label
|
|
163
|
+
if (periodLabelEl && data.periodLabel) {
|
|
164
|
+
periodLabelEl.textContent = `/ ${data.periodLabel}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Update breakdown
|
|
168
|
+
if (baseEl) {
|
|
169
|
+
baseEl.textContent = formatCurrency(data.baseCost);
|
|
170
|
+
}
|
|
171
|
+
if (overageEl) {
|
|
172
|
+
overageEl.textContent = formatCurrency(data.overageCost);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Update badge
|
|
176
|
+
if (badgeEl) {
|
|
177
|
+
badgeEl.className = `px-2 py-0.5 text-xs font-medium rounded-full ${getBadgeClasses(data.overageCost, data.baseCost)}`;
|
|
178
|
+
badgeEl.textContent = getBadgeText(data.overageCost, data.baseCost);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Update overage percentage text
|
|
182
|
+
if (overagePctEl) {
|
|
183
|
+
const pct = data.totalCost > 0 ? (data.overageCost / data.totalCost) * 100 : 0;
|
|
184
|
+
overagePctEl.textContent = `${pct.toFixed(0)}% overage`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Update progress bars
|
|
188
|
+
if (baseBarEl && overageBarEl && data.totalCost > 0) {
|
|
189
|
+
const basePct = (data.baseCost / data.totalCost) * 100;
|
|
190
|
+
const overagePct = (data.overageCost / data.totalCost) * 100;
|
|
191
|
+
baseBarEl.style.width = `${basePct}%`;
|
|
192
|
+
overageBarEl.style.width = `${overagePct}%`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Update overage dot color
|
|
196
|
+
if (overageDotEl) {
|
|
197
|
+
overageDotEl.className = `w-2 h-2 rounded-full ${getOverageDotColor(data.overageCost)}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Update note
|
|
201
|
+
if (noteEl && data.note) {
|
|
202
|
+
noteEl.textContent = data.note;
|
|
203
|
+
} else if (noteEl && data.isProrated) {
|
|
204
|
+
noteEl.textContent = 'Workers Paid Plan (prorated for period)';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Expose globally for page scripts
|
|
209
|
+
(
|
|
210
|
+
window as unknown as { updateOverageCostCard: typeof updateOverageCostCard }
|
|
211
|
+
).updateOverageCostCard = updateOverageCostCard;
|
|
212
|
+
</script>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* PlanUtilizationCard - Shows highest service utilisation %
|
|
4
|
+
*
|
|
5
|
+
* Part of task-usage-rebuild: Single Pane of Glass
|
|
6
|
+
* Displays the service with highest monthly limit utilisation
|
|
7
|
+
*/
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<div
|
|
11
|
+
class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 shadow-sm h-full flex flex-col justify-between"
|
|
12
|
+
>
|
|
13
|
+
<!-- Header -->
|
|
14
|
+
<div class="flex items-center gap-2 mb-3">
|
|
15
|
+
<div
|
|
16
|
+
class="w-8 h-8 rounded-lg bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center"
|
|
17
|
+
>
|
|
18
|
+
<svg
|
|
19
|
+
class="w-4 h-4 text-purple-600 dark:text-purple-400"
|
|
20
|
+
fill="none"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
viewBox="0 0 24 24"
|
|
23
|
+
>
|
|
24
|
+
<path
|
|
25
|
+
stroke-linecap="round"
|
|
26
|
+
stroke-linejoin="round"
|
|
27
|
+
stroke-width="2"
|
|
28
|
+
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
|
29
|
+
></path>
|
|
30
|
+
</svg>
|
|
31
|
+
</div>
|
|
32
|
+
<div>
|
|
33
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Plan Utilisation</h3>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Main metric -->
|
|
38
|
+
<div class="mb-3">
|
|
39
|
+
<div class="flex items-baseline gap-2">
|
|
40
|
+
<span id="utilization-pct" class="text-3xl font-bold text-gray-900 dark:text-gray-100"
|
|
41
|
+
>--%</span
|
|
42
|
+
>
|
|
43
|
+
<span id="utilization-service" class="text-sm text-gray-500 dark:text-gray-400"
|
|
44
|
+
>Loading...</span
|
|
45
|
+
>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Progress bar -->
|
|
50
|
+
<div class="mb-2">
|
|
51
|
+
<div class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
52
|
+
<div
|
|
53
|
+
id="utilization-bar"
|
|
54
|
+
class="h-full bg-purple-500 rounded-full transition-all duration-500"
|
|
55
|
+
style="width: 0%"
|
|
56
|
+
>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Status label -->
|
|
62
|
+
<div class="flex items-center justify-between text-xs">
|
|
63
|
+
<span id="utilization-usage" class="text-gray-500 dark:text-gray-400">-- / -- used</span>
|
|
64
|
+
<span id="utilization-status" class="font-medium text-gray-600 dark:text-gray-300">--</span>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Billing Countdown (compact inline) -->
|
|
68
|
+
<div class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
|
69
|
+
<div class="flex items-center justify-between text-xs">
|
|
70
|
+
<div class="flex items-center gap-1.5 text-gray-500 dark:text-gray-400">
|
|
71
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
72
|
+
<path
|
|
73
|
+
stroke-linecap="round"
|
|
74
|
+
stroke-linejoin="round"
|
|
75
|
+
stroke-width="2"
|
|
76
|
+
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
77
|
+
></path>
|
|
78
|
+
</svg>
|
|
79
|
+
<span id="billing-countdown-text">-- days until reset</span>
|
|
80
|
+
</div>
|
|
81
|
+
<span id="billing-period-label" class="text-gray-400 dark:text-gray-500">
|
|
82
|
+
Jan 1 - Jan 31
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
interface ServiceData {
|
|
90
|
+
name: string;
|
|
91
|
+
usagePct: number;
|
|
92
|
+
current: number;
|
|
93
|
+
limit: number;
|
|
94
|
+
unit: string;
|
|
95
|
+
status: 'normal' | 'warning' | 'high' | 'critical';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatNumber(num: number): string {
|
|
99
|
+
if (num >= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(1)}B`;
|
|
100
|
+
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
|
|
101
|
+
if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`;
|
|
102
|
+
return num.toFixed(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getStatusColor(pct: number): string {
|
|
106
|
+
if (pct >= 90) return 'bg-red-500';
|
|
107
|
+
if (pct >= 75) return 'bg-orange-500';
|
|
108
|
+
if (pct >= 50) return 'bg-yellow-500';
|
|
109
|
+
return 'bg-purple-500';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getStatusLabel(pct: number): string {
|
|
113
|
+
if (pct >= 90) return 'Critical';
|
|
114
|
+
if (pct >= 75) return 'High';
|
|
115
|
+
if (pct >= 50) return 'Warning';
|
|
116
|
+
return 'Normal';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function updatePlanUtilization(services: ServiceData[]) {
|
|
120
|
+
if (!services || services.length === 0) return;
|
|
121
|
+
|
|
122
|
+
// Find highest utilisation service
|
|
123
|
+
const highest = services.reduce(
|
|
124
|
+
(max, service) => (service.usagePct > max.usagePct ? service : max),
|
|
125
|
+
services[0]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const pctEl = document.getElementById('utilization-pct');
|
|
129
|
+
const serviceEl = document.getElementById('utilization-service');
|
|
130
|
+
const barEl = document.getElementById('utilization-bar');
|
|
131
|
+
const usageEl = document.getElementById('utilization-usage');
|
|
132
|
+
const statusEl = document.getElementById('utilization-status');
|
|
133
|
+
|
|
134
|
+
// Cap extreme percentages at 999% for display
|
|
135
|
+
const displayPct = highest.usagePct > 999 ? '>999' : highest.usagePct.toFixed(0);
|
|
136
|
+
if (pctEl) pctEl.textContent = `${displayPct}%`;
|
|
137
|
+
if (serviceEl) {
|
|
138
|
+
// Show overage indicator when over 100%
|
|
139
|
+
serviceEl.textContent =
|
|
140
|
+
highest.usagePct > 100 ? `${highest.name} (over limit!)` : highest.name;
|
|
141
|
+
}
|
|
142
|
+
if (barEl) {
|
|
143
|
+
barEl.style.width = `${Math.min(highest.usagePct, 100)}%`;
|
|
144
|
+
barEl.className = `h-full rounded-full transition-all duration-500 ${getStatusColor(highest.usagePct)}`;
|
|
145
|
+
}
|
|
146
|
+
if (usageEl) {
|
|
147
|
+
usageEl.textContent = `${formatNumber(highest.current)} / ${formatNumber(highest.limit)} ${highest.unit}`;
|
|
148
|
+
}
|
|
149
|
+
if (statusEl) {
|
|
150
|
+
statusEl.textContent = getStatusLabel(highest.usagePct);
|
|
151
|
+
const statusColors: Record<string, string> = {
|
|
152
|
+
Critical: 'text-red-600 dark:text-red-400',
|
|
153
|
+
High: 'text-orange-600 dark:text-orange-400',
|
|
154
|
+
Warning: 'text-yellow-600 dark:text-yellow-400',
|
|
155
|
+
Normal: 'text-green-600 dark:text-green-400',
|
|
156
|
+
};
|
|
157
|
+
statusEl.className = `font-medium ${statusColors[getStatusLabel(highest.usagePct)]}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Billing countdown update function
|
|
162
|
+
interface BillingData {
|
|
163
|
+
daysRemaining: number;
|
|
164
|
+
periodFormatted: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function updateBillingCountdown(data: BillingData) {
|
|
168
|
+
const textEl = document.getElementById('billing-countdown-text');
|
|
169
|
+
const periodEl = document.getElementById('billing-period-label');
|
|
170
|
+
|
|
171
|
+
if (textEl) {
|
|
172
|
+
if (data.daysRemaining <= 0) {
|
|
173
|
+
textEl.textContent = 'Billing reset today';
|
|
174
|
+
} else if (data.daysRemaining === 1) {
|
|
175
|
+
textEl.textContent = '1 day until reset';
|
|
176
|
+
} else {
|
|
177
|
+
textEl.textContent = `${data.daysRemaining} days until reset`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (periodEl && data.periodFormatted) {
|
|
182
|
+
periodEl.textContent = data.periodFormatted;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Expose globally for index.astro to call
|
|
187
|
+
(
|
|
188
|
+
window as unknown as { updatePlanUtilization: typeof updatePlanUtilization }
|
|
189
|
+
).updatePlanUtilization = updatePlanUtilization;
|
|
190
|
+
(
|
|
191
|
+
window as unknown as { updateBillingCountdown: typeof updateBillingCountdown }
|
|
192
|
+
).updateBillingCountdown = updateBillingCountdown;
|
|
193
|
+
</script>
|