@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.
Files changed (122) hide show
  1. package/README.md +2 -5
  2. package/dist/check-upgrade.d.ts +29 -0
  3. package/dist/check-upgrade.js +97 -0
  4. package/dist/index.js +59 -4
  5. package/dist/manifest.d.ts +2 -0
  6. package/dist/scaffold.js +5 -1
  7. package/dist/templates.d.ts +6 -1
  8. package/dist/templates.js +141 -3
  9. package/dist/upgrade.d.ts +1 -0
  10. package/dist/upgrade.js +21 -2
  11. package/package.json +1 -1
  12. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  13. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  14. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  15. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  16. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  17. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  18. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  19. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  20. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  21. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  22. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  23. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  24. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  25. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  26. package/templates/full/dashboard/src/pages/map.astro +561 -0
  27. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  28. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  29. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  30. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  31. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  32. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  33. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  34. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  35. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  36. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  37. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  38. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  39. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  40. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  41. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  42. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  43. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  44. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  45. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  46. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  47. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  48. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  49. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  50. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  51. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  52. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  53. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  54. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  55. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  56. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  57. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  58. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  59. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  60. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  61. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  62. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  63. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  64. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  65. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  66. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  67. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  68. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  69. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  70. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  71. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  72. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  73. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  74. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  75. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  76. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  77. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  78. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  79. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  80. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  81. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  82. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  83. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  84. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  85. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  86. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  87. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  88. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  89. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  90. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  91. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  92. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  93. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  94. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  95. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  96. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  97. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  98. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  99. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  100. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  101. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  102. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  103. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  104. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  105. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  106. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  107. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  108. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  109. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  110. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  111. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  112. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  113. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  114. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  115. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  116. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  117. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  118. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  119. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  120. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  121. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  122. 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>