@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.
Files changed (185) hide show
  1. package/README.md +4 -7
  2. package/dist/templates.d.ts +1 -1
  3. package/dist/templates.js +206 -4
  4. package/package.json +1 -1
  5. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  6. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  7. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  8. package/templates/full/dashboard/src/components/reports/DigestStats.tsx +151 -0
  9. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  10. package/templates/full/dashboard/src/components/reports/HealthTrendsReport.tsx +192 -0
  11. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  12. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  13. package/templates/full/dashboard/src/components/usage/AIModelBreakdown.tsx +364 -0
  14. package/templates/full/dashboard/src/components/usage/unified/Recommendations.tsx +149 -0
  15. package/templates/full/dashboard/src/lib/cloudflare/alerting.ts +486 -0
  16. package/templates/full/dashboard/src/lib/cloudflare/graphql.ts +4785 -0
  17. package/templates/full/dashboard/src/lib/cloudflare/project-registry.ts +451 -0
  18. package/templates/full/dashboard/src/lib/notifications/api.ts +197 -0
  19. package/templates/full/dashboard/src/lib/notifications/types.ts.hbs +97 -0
  20. package/templates/full/dashboard/src/lib/patterns/api.ts +120 -0
  21. package/templates/full/dashboard/src/lib/patterns/types.ts +127 -0
  22. package/templates/full/dashboard/src/lib/reports/types.ts +231 -0
  23. package/templates/full/dashboard/src/lib/search/api.ts +258 -0
  24. package/templates/full/dashboard/src/lib/search/types.ts.hbs +115 -0
  25. package/templates/full/dashboard/src/lib/settings/api.ts.hbs +201 -0
  26. package/templates/full/dashboard/src/lib/settings/types.ts.hbs +104 -0
  27. package/templates/full/dashboard/src/lib/usage/allowance-config.ts.hbs +547 -0
  28. package/templates/full/dashboard/src/lib/usage/providers.ts +331 -0
  29. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  30. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  31. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  32. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  33. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  34. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  35. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  36. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  37. package/templates/full/dashboard/src/pages/map.astro +561 -0
  38. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  39. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  40. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  41. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  42. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  43. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  44. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  45. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  46. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  47. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  48. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  49. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  50. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  51. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  52. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  53. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  54. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  55. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  56. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  57. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  58. package/templates/shared/dashboard/src/components/reports/ReportInfoButton.tsx +98 -0
  59. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  60. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  61. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  62. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  63. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  64. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  65. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  66. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  67. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  68. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  69. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  70. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  71. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  72. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  73. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  74. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  75. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  76. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  77. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  78. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  79. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  80. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  81. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  82. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  83. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  84. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  85. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  86. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  87. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  88. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  89. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  90. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  91. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  92. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  93. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  94. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  95. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  96. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  97. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  98. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  99. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  100. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  101. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  102. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  103. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  104. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  105. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  106. package/templates/shared/dashboard/src/components/usage/react/DashboardShell.tsx +263 -0
  107. package/templates/shared/dashboard/src/components/usage/react/StatusBadge.tsx +77 -0
  108. package/templates/shared/dashboard/src/components/usage/react/UsageChart.tsx +391 -0
  109. package/templates/shared/dashboard/src/components/usage/react/index.ts.hbs +30 -0
  110. package/templates/shared/dashboard/src/components/usage/react/types.ts +137 -0
  111. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  112. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  113. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  114. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  115. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  116. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  117. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  118. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  119. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  120. package/templates/shared/dashboard/src/components/usage/transformers.ts +478 -0
  121. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  122. package/templates/shared/dashboard/src/components/usage/unified/AlertBanner.tsx +172 -0
  123. package/templates/shared/dashboard/src/components/usage/unified/HeroCardsRow.tsx +757 -0
  124. package/templates/shared/dashboard/src/components/usage/unified/LiveHeader.tsx +169 -0
  125. package/templates/shared/dashboard/src/components/usage/unified/ProjectsTable.tsx +448 -0
  126. package/templates/shared/dashboard/src/components/usage/unified/ResourceBreakdown.tsx +236 -0
  127. package/templates/shared/dashboard/src/components/usage/unified/Sparkline.tsx +127 -0
  128. package/templates/shared/dashboard/src/components/usage/unified/UnifiedShell.tsx +893 -0
  129. package/templates/shared/dashboard/src/components/usage/unified/index.ts.hbs +50 -0
  130. package/templates/shared/dashboard/src/components/usage/unified/types.ts +416 -0
  131. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  132. package/templates/shared/dashboard/src/lib/cloudflare/analytics.ts +310 -0
  133. package/templates/shared/dashboard/src/lib/cloudflare/d1.ts +55 -0
  134. package/templates/shared/dashboard/src/lib/cloudflare/index.ts.hbs +120 -0
  135. package/templates/shared/dashboard/src/lib/infrastructure/types.ts +116 -0
  136. package/templates/shared/dashboard/src/lib/usage/fetchWithDedup.ts +101 -0
  137. package/templates/shared/dashboard/src/lib/usage/index.ts.hbs +12 -0
  138. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  139. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  140. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  141. package/templates/shared/tests/e2e/usage-api.test.ts +909 -0
  142. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  143. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  144. package/templates/shared/tests/helpers/mock-storage.ts +166 -0
  145. package/templates/shared/tests/integration/kv-cache.test.ts +252 -0
  146. package/templates/shared/tests/integration/platform-usage.test.ts +956 -0
  147. package/templates/shared/tests/unit/billing.test.ts +331 -0
  148. package/templates/shared/tests/unit/cloudflare/graphql.test.ts +217 -0
  149. package/templates/shared/tests/unit/components/usage-transformers.test.ts +473 -0
  150. package/templates/shared/tests/unit/control.test.ts +226 -0
  151. package/templates/shared/tests/unit/cost-calculator.test.ts +141 -0
  152. package/templates/shared/tests/unit/economics.test.ts +365 -0
  153. package/templates/shared/tests/unit/telemetry-sampling.test.ts +401 -0
  154. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  155. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  156. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  157. package/templates/standard/dashboard/src/components/reports/CircuitBreakerReport.tsx +474 -0
  158. package/templates/standard/dashboard/src/components/reports/CostTrendsReport.tsx +229 -0
  159. package/templates/standard/dashboard/src/components/reports/ErrorTrendsReport.tsx +244 -0
  160. package/templates/standard/dashboard/src/components/reports/ProjectHealthTable.tsx +251 -0
  161. package/templates/standard/dashboard/src/components/reports/WarningDigestsTable.tsx +298 -0
  162. package/templates/standard/dashboard/src/components/usage/react/UsageTable.tsx +385 -0
  163. package/templates/standard/dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx +305 -0
  164. package/templates/standard/dashboard/src/components/usage/unified/FeatureBudgets.tsx +472 -0
  165. package/templates/standard/dashboard/src/lib/errors/api.ts +84 -0
  166. package/templates/standard/dashboard/src/lib/errors/types.ts +75 -0
  167. package/templates/standard/dashboard/src/lib/infrastructure/api.ts +141 -0
  168. package/templates/standard/dashboard/src/lib/infrastructure/gatus.ts.hbs +112 -0
  169. package/templates/standard/dashboard/src/lib/services/proxy/index.ts +20 -0
  170. package/templates/standard/dashboard/src/lib/services/proxy/proxy.ts +244 -0
  171. package/templates/standard/dashboard/src/lib/services/proxy/types.ts +81 -0
  172. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  173. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  174. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  175. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  176. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  177. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  178. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  179. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  180. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  181. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
  182. package/templates/standard/tests/integration/platform-sentinel.test.ts +497 -0
  183. package/templates/standard/tests/unit/cloudflare/alerting.test.ts +480 -0
  184. package/templates/standard/tests/unit/error-collector/dedup.test.ts +350 -0
  185. package/templates/standard/tests/unit/error-collector/github.test.ts +187 -0
@@ -0,0 +1,547 @@
1
+ /**
2
+ * Cloudflare Allowance & Licensing Configuration
3
+ *
4
+ * Defines monthly limits for Cloudflare services to track "Usage vs. Included Thresholds".
5
+ * These values represent the Workers Paid plan allowances and free tier limits.
6
+ *
7
+ * @see https://developers.cloudflare.com/workers/platform/pricing/
8
+ */
9
+
10
+ /**
11
+ * Service type identifiers
12
+ */
13
+ export type ServiceType =
14
+ | 'workers'
15
+ | 'd1'
16
+ | 'kv'
17
+ | 'r2'
18
+ | 'durableObjects'
19
+ | 'vectorize'
20
+ | 'aiGateway'
21
+ | 'workersAI'
22
+ | 'pages'
23
+ | 'queues';
24
+
25
+ /**
26
+ * Utilization status based on percentage thresholds
27
+ */
28
+ export type UtilizationStatus = 'green' | 'yellow' | 'red';
29
+
30
+ /**
31
+ * Service allowance definition
32
+ */
33
+ export interface ServiceAllowance {
34
+ /** Human-readable service name */
35
+ name: string;
36
+ /** Monthly allowance value (in native units) */
37
+ monthlyLimit: number;
38
+ /** Unit of measurement */
39
+ unit: string;
40
+ /** Whether this is a paid plan limit (vs free tier) */
41
+ isPaidPlan: boolean;
42
+ /** Description of the allowance */
43
+ description: string;
44
+ }
45
+
46
+ /**
47
+ * Project-specific allowance overrides
48
+ */
49
+ export interface ProjectAllowance {
50
+ projectId: string;
51
+ projectName: string;
52
+ /** Primary resource type for this project (e.g., D1 writes for database-heavy projects) */
53
+ primaryResource: ServiceType;
54
+ /** Custom limits that override account-level defaults */
55
+ overrides?: Partial<Record<ServiceType, number>>;
56
+ }
57
+
58
+ /**
59
+ * Account-level Cloudflare service allowances (Workers Paid Plan)
60
+ *
61
+ * Based on Cloudflare pricing as of January 2026:
62
+ * - Workers Paid: $5/month includes 10M requests
63
+ * - D1: 50M writes/month for paid accounts
64
+ * - R2: 10 GB Class A operations included
65
+ */
66
+ export const CF_ALLOWANCES: Record<ServiceType, ServiceAllowance> = {
67
+ workers: {
68
+ name: 'Workers Requests',
69
+ monthlyLimit: 10_000_000, // 10M requests/month included
70
+ unit: 'requests',
71
+ isPaidPlan: true,
72
+ description: '10M requests included with Workers Paid ($5/mo)',
73
+ },
74
+ d1: {
75
+ name: 'D1 Writes',
76
+ monthlyLimit: 50_000_000, // 50M writes/month threshold (primary metric for utilization)
77
+ unit: 'rows written',
78
+ isPaidPlan: true,
79
+ description: '25B rows read + 50M rows written per month included',
80
+ },
81
+ kv: {
82
+ name: 'KV Writes',
83
+ monthlyLimit: 1_000_000, // 1M writes/month threshold (primary metric for utilization)
84
+ unit: 'writes',
85
+ isPaidPlan: true,
86
+ description: '10M reads + 1M writes/deletes/lists per month included',
87
+ },
88
+ r2: {
89
+ name: 'R2 Storage',
90
+ monthlyLimit: 10_000_000_000, // 10GB storage (primary metric for utilization)
91
+ unit: 'bytes',
92
+ isPaidPlan: true,
93
+ description: '10GB storage + 1M Class A + 10M Class B ops per month included',
94
+ },
95
+ durableObjects: {
96
+ name: 'Durable Objects Requests',
97
+ monthlyLimit: 1_000_000, // 1M requests/month Workers Paid Plan
98
+ unit: 'requests',
99
+ isPaidPlan: true,
100
+ description: '1M requests included in Workers Paid Plan',
101
+ },
102
+ vectorize: {
103
+ name: 'Vectorize Stored Dimensions',
104
+ monthlyLimit: 10_000_000, // 10M stored dimensions/month Workers Paid Plan
105
+ unit: 'dimensions',
106
+ isPaidPlan: true,
107
+ description:
108
+ '10M stored dimensions + 50M queried dimensions per month included in Workers Paid Plan',
109
+ },
110
+ aiGateway: {
111
+ name: 'AI Gateway Requests',
112
+ monthlyLimit: Infinity, // Unlimited (free service)
113
+ unit: 'requests',
114
+ isPaidPlan: false,
115
+ description: 'AI Gateway is free (cost is from underlying AI provider)',
116
+ },
117
+ // CONSERVATIVE: Set to 0 allowance for accurate billing visibility on Workers Paid Plan.
118
+ // Cloudflare provides a 10K neurons/day free tier (resets daily at midnight UTC),
119
+ // but we report from first neuron to ensure costs are never hidden.
120
+ // Better to over-report than under-report for budget tracking.
121
+ workersAI: {
122
+ name: 'Workers AI Neurons',
123
+ monthlyLimit: 0, // Pay-as-you-go from first neuron on Workers Paid
124
+ unit: 'neurons',
125
+ isPaidPlan: true,
126
+ description:
127
+ 'Pay-as-you-go: $0.011/1K neurons. No paid plan inclusion (free tier may apply but not tracked).',
128
+ },
129
+ pages: {
130
+ name: 'Pages Builds',
131
+ monthlyLimit: 500, // 500 builds/month free
132
+ unit: 'builds',
133
+ isPaidPlan: false,
134
+ description: '500 builds per month in free tier',
135
+ },
136
+ queues: {
137
+ name: 'Queues Messages',
138
+ monthlyLimit: 1_000_000, // 1M messages/month free
139
+ unit: 'messages',
140
+ isPaidPlan: false,
141
+ description: '1M messages per month in free tier',
142
+ },
143
+ };
144
+
145
+ /**
146
+ * Daily limits (derived from monthly for rate limiting purposes)
147
+ *
148
+ * IMPORTANT: Most services reset MONTHLY, so daily limit = monthly / 30.
149
+ * Services with 0 monthly allowance (pay-as-you-go) have 0 daily limit.
150
+ */
151
+ export const CF_DAILY_LIMITS: Record<ServiceType, number> = {
152
+ workers: Math.floor(CF_ALLOWANCES.workers.monthlyLimit / 30), // ~333K/day
153
+ d1: Math.floor(CF_ALLOWANCES.d1.monthlyLimit / 30), // ~1.67M/day
154
+ kv: Math.floor(CF_ALLOWANCES.kv.monthlyLimit / 30), // ~33K/day
155
+ r2: Math.floor(CF_ALLOWANCES.r2.monthlyLimit / 30),
156
+ durableObjects: Math.floor(CF_ALLOWANCES.durableObjects.monthlyLimit / 30),
157
+ vectorize: 0, // Pay-as-you-go (no free tier tracked)
158
+ aiGateway: Infinity,
159
+ workersAI: 0, // Pay-as-you-go (no free tier tracked)
160
+ pages: Math.floor(CF_ALLOWANCES.pages.monthlyLimit / 30),
161
+ queues: Math.floor(CF_ALLOWANCES.queues.monthlyLimit / 30),
162
+ };
163
+
164
+ /**
165
+ * Project-specific configurations with primary resource assignments
166
+ *
167
+ * NOTE: Project overrides removed (2026-01-15). Utilization now calculated
168
+ * against account-level CF_ALLOWANCES to match actual Cloudflare billing.
169
+ * If project-level budgets are needed in future, implement via D1 usage_settings
170
+ * table with UI configuration in ThresholdSettings.
171
+ */
172
+ export const PROJECT_ALLOWANCES: ProjectAllowance[] = [
173
+ {
174
+ projectId: '{{projectSlug}}',
175
+ projectName: '{{projectName}}',
176
+ primaryResource: 'd1',
177
+ // Utilization uses CF_ALLOWANCES.d1 (50M writes/month)
178
+ },
179
+ {
180
+ projectId: 'platform',
181
+ projectName: 'Platform',
182
+ primaryResource: 'd1',
183
+ // Utilization uses CF_ALLOWANCES.d1 (50M writes/month)
184
+ },
185
+ ];
186
+
187
+ /**
188
+ * Utilization threshold percentages for traffic light status
189
+ */
190
+ export const UTILIZATION_THRESHOLDS = {
191
+ /** Green zone: < 70% utilization */
192
+ green: 70,
193
+ /** Yellow zone: 70-90% utilization */
194
+ yellow: 90,
195
+ /** Red zone: > 90% utilization (or overage) */
196
+ red: 100,
197
+ } as const;
198
+
199
+ /**
200
+ * Project data from D1 registry (via /api/usage/projects)
201
+ */
202
+ interface D1Project {
203
+ projectId: string;
204
+ displayName: string;
205
+ primaryResource: string | null;
206
+ customLimit: number | null;
207
+ status: string;
208
+ }
209
+
210
+ /**
211
+ * Map D1 resource type to ServiceType
212
+ */
213
+ function mapResourceType(d1Resource: string | null): ServiceType {
214
+ if (!d1Resource) return 'workers';
215
+
216
+ const mapping: Record<string, ServiceType> = {
217
+ worker: 'workers',
218
+ workers: 'workers',
219
+ d1: 'd1',
220
+ kv: 'kv',
221
+ r2: 'r2',
222
+ vectorize: 'vectorize',
223
+ ai_gateway: 'aiGateway',
224
+ workers_ai: 'workersAI',
225
+ workersai: 'workersAI',
226
+ pages: 'pages',
227
+ queue: 'queues',
228
+ durable_object: 'durableObjects',
229
+ };
230
+
231
+ return mapping[d1Resource.toLowerCase()] ?? 'workers';
232
+ }
233
+
234
+ /**
235
+ * Fetch project allowances from D1 registry via API.
236
+ * Falls back to static PROJECT_ALLOWANCES if the API fails.
237
+ *
238
+ * @param baseUrl - Base URL for the API
239
+ * @param fetchFn - Optional fetch function (for SSR environments)
240
+ * @returns ProjectAllowance array
241
+ */
242
+ export async function fetchProjectAllowances(
243
+ baseUrl: string = '',
244
+ fetchFn: typeof fetch = fetch
245
+ ): Promise<ProjectAllowance[]> {
246
+ try {
247
+ const response = await fetchFn(`${baseUrl}/api/usage/projects`, {
248
+ credentials: 'include',
249
+ headers: {
250
+ Accept: 'application/json',
251
+ },
252
+ });
253
+
254
+ if (!response.ok) {
255
+ console.warn(`[allowance-config] API returned ${response.status}, using static fallback`);
256
+ return PROJECT_ALLOWANCES;
257
+ }
258
+
259
+ const json = (await response.json()) as {
260
+ success: boolean;
261
+ projects?: D1Project[];
262
+ };
263
+
264
+ if (!json.success || !json.projects) {
265
+ console.warn('[allowance-config] Invalid API response, using static fallback');
266
+ return PROJECT_ALLOWANCES;
267
+ }
268
+
269
+ // Convert D1 projects to ProjectAllowance format
270
+ const dynamicAllowances: ProjectAllowance[] = json.projects
271
+ .filter((p) => p.status === 'active' && p.primaryResource)
272
+ .map((p) => {
273
+ const primaryResource = mapResourceType(p.primaryResource);
274
+ const allowance: ProjectAllowance = {
275
+ projectId: p.projectId,
276
+ projectName: p.displayName,
277
+ primaryResource,
278
+ };
279
+
280
+ // Add custom limit as override if specified
281
+ if (p.customLimit && p.customLimit > 0) {
282
+ allowance.overrides = {
283
+ [primaryResource]: p.customLimit,
284
+ };
285
+ }
286
+
287
+ return allowance;
288
+ });
289
+
290
+ // If we got valid data, return it; otherwise fall back
291
+ if (dynamicAllowances.length > 0) {
292
+ console.log(
293
+ `[allowance-config] Loaded ${dynamicAllowances.length} projects from D1 registry`
294
+ );
295
+ return dynamicAllowances;
296
+ }
297
+
298
+ return PROJECT_ALLOWANCES;
299
+ } catch (error) {
300
+ console.warn('[allowance-config] Failed to fetch from API, using static fallback:', error);
301
+ return PROJECT_ALLOWANCES;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Get project allowances (sync version, returns static config)
307
+ * Use fetchProjectAllowances() for dynamic D1-backed data.
308
+ */
309
+ export function getStaticProjectAllowances(): ProjectAllowance[] {
310
+ return PROJECT_ALLOWANCES;
311
+ }
312
+
313
+ /**
314
+ * Calculate utilization percentage
315
+ */
316
+ export function calculateUtilizationPct(current: number, limit: number): number {
317
+ if (limit === Infinity || limit === 0) return 0;
318
+ return Math.min((current / limit) * 100, 999); // Cap at 999% for display
319
+ }
320
+
321
+ /**
322
+ * Get utilization status (traffic light) based on percentage
323
+ */
324
+ export function getUtilizationStatus(percentage: number): UtilizationStatus {
325
+ if (percentage < UTILIZATION_THRESHOLDS.green) return 'green';
326
+ if (percentage < UTILIZATION_THRESHOLDS.yellow) return 'yellow';
327
+ return 'red';
328
+ }
329
+
330
+ /**
331
+ * Get the effective limit for a project/service combination
332
+ */
333
+ export function getEffectiveLimit(_projectId: string, serviceType: ServiceType): number {
334
+ // Always use account-level CF allowances for utilization calculations.
335
+ // This matches actual Cloudflare billing and provides accurate visibility.
336
+ // Project-specific budgets were removed 2026-01-15 - see PROJECT_ALLOWANCES comment.
337
+ return CF_ALLOWANCES[serviceType].monthlyLimit;
338
+ }
339
+
340
+ /**
341
+ * Calculate utilization data for a project
342
+ */
343
+ export interface ProjectUtilization {
344
+ projectId: string;
345
+ projectName: string;
346
+ primaryResource: ServiceType;
347
+ currentUsage: number;
348
+ monthlyLimit: number;
349
+ utilizationPct: number;
350
+ status: UtilizationStatus;
351
+ unit: string;
352
+ }
353
+
354
+ /**
355
+ * Build utilization data for a project given current usage
356
+ */
357
+ export function buildProjectUtilization(
358
+ projectId: string,
359
+ currentUsage: Record<ServiceType, number>
360
+ ): ProjectUtilization | null {
361
+ const projectConfig = PROJECT_ALLOWANCES.find((p) => p.projectId === projectId);
362
+ if (!projectConfig) return null;
363
+
364
+ const primaryResource = projectConfig.primaryResource;
365
+ const usage = currentUsage[primaryResource] ?? 0;
366
+ const limit = getEffectiveLimit(projectId, primaryResource);
367
+ const utilizationPct = calculateUtilizationPct(usage, limit);
368
+
369
+ return {
370
+ projectId,
371
+ projectName: projectConfig.projectName,
372
+ primaryResource,
373
+ currentUsage: usage,
374
+ monthlyLimit: limit,
375
+ utilizationPct,
376
+ status: getUtilizationStatus(utilizationPct),
377
+ unit: CF_ALLOWANCES[primaryResource].unit,
378
+ };
379
+ }
380
+
381
+ /**
382
+ * Calculate aggregate account-level utilization across all services
383
+ */
384
+ export interface AccountUtilization {
385
+ totalMTDCost: number;
386
+ projectedMonthlyCost: number;
387
+ dailyBurnRate: number;
388
+ daysIntoMonth: number;
389
+ services: Record<
390
+ ServiceType,
391
+ {
392
+ current: number;
393
+ limit: number;
394
+ pct: number;
395
+ status: UtilizationStatus;
396
+ }
397
+ >;
398
+ }
399
+
400
+ /**
401
+ * Get the list of tracked project IDs
402
+ */
403
+ export function getTrackedProjectIds(): string[] {
404
+ return PROJECT_ALLOWANCES.map((p) => p.projectId);
405
+ }
406
+
407
+ /**
408
+ * Get project display name by ID
409
+ */
410
+ export function getProjectDisplayName(projectId: string): string {
411
+ const project = PROJECT_ALLOWANCES.find((p) => p.projectId === projectId);
412
+ return project?.projectName ?? projectId;
413
+ }
414
+
415
+ /**
416
+ * Simplified allowances for worker usage (limit + unit only)
417
+ * Used by platform-usage worker for utilization calculations.
418
+ */
419
+ export type SimpleAllowanceType =
420
+ | 'workers'
421
+ | 'd1'
422
+ | 'kv'
423
+ | 'r2'
424
+ | 'vectorize'
425
+ | 'workersAI'
426
+ | 'durableObjects'
427
+ | 'queues';
428
+
429
+ export const CF_SIMPLE_ALLOWANCES: Record<SimpleAllowanceType, { limit: number; unit: string }> = {
430
+ workers: { limit: CF_ALLOWANCES.workers.monthlyLimit, unit: CF_ALLOWANCES.workers.unit },
431
+ d1: { limit: CF_ALLOWANCES.d1.monthlyLimit, unit: CF_ALLOWANCES.d1.unit },
432
+ kv: { limit: CF_ALLOWANCES.kv.monthlyLimit, unit: CF_ALLOWANCES.kv.unit },
433
+ r2: { limit: CF_ALLOWANCES.r2.monthlyLimit, unit: CF_ALLOWANCES.r2.unit },
434
+ vectorize: { limit: CF_ALLOWANCES.vectorize.monthlyLimit, unit: CF_ALLOWANCES.vectorize.unit },
435
+ workersAI: { limit: CF_ALLOWANCES.workersAI.monthlyLimit, unit: CF_ALLOWANCES.workersAI.unit },
436
+ durableObjects: {
437
+ limit: CF_ALLOWANCES.durableObjects.monthlyLimit,
438
+ unit: CF_ALLOWANCES.durableObjects.unit,
439
+ },
440
+ queues: {
441
+ limit: CF_ALLOWANCES.queues.monthlyLimit,
442
+ unit: CF_ALLOWANCES.queues.unit,
443
+ },
444
+ };
445
+
446
+ // =============================================================================
447
+ // GITHUB ALLOWANCES & PRICING
448
+ // =============================================================================
449
+
450
+ /**
451
+ * GitHub plan allowances (defaults by plan tier)
452
+ * These are dynamically fetched from the API but have fallbacks here.
453
+ *
454
+ * @see https://docs.github.com/en/billing/managing-billing-for-your-products/managing-billing-for-github-actions/about-billing-for-github-actions
455
+ */
456
+ export const GITHUB_ALLOWANCES = {
457
+ /** GitHub Free plan */
458
+ free: {
459
+ actionsMinutes: 2000, // 2,000 minutes/month (private repos)
460
+ actionsStorageGb: 0.5, // 500 MB storage
461
+ packagesStorageGb: 0.5, // 500 MB packages
462
+ packagesBandwidthGb: 1, // 1 GB bandwidth
463
+ lfsStorageGb: 1, // 1 GiB storage
464
+ lfsBandwidthGb: 1, // 1 GiB bandwidth/month
465
+ },
466
+ /** GitHub Pro plan (individual) */
467
+ pro: {
468
+ actionsMinutes: 3000, // 3,000 minutes/month
469
+ actionsStorageGb: 2, // 2 GB storage
470
+ packagesStorageGb: 2, // 2 GB packages
471
+ packagesBandwidthGb: 10, // 10 GB bandwidth
472
+ lfsStorageGb: 1, // 1 GiB storage
473
+ lfsBandwidthGb: 1, // 1 GiB bandwidth/month
474
+ },
475
+ /** GitHub Team plan */
476
+ team: {
477
+ actionsMinutes: 3000, // 3,000 minutes/month
478
+ actionsStorageGb: 2, // 2 GB storage
479
+ packagesStorageGb: 2, // 2 GB packages
480
+ packagesBandwidthGb: 10, // 10 GB bandwidth
481
+ lfsStorageGb: 1, // 1 GiB storage
482
+ lfsBandwidthGb: 1, // 1 GiB bandwidth/month
483
+ },
484
+ /** GitHub Enterprise Cloud plan */
485
+ enterprise: {
486
+ actionsMinutes: 50000, // 50,000 minutes/month
487
+ actionsStorageGb: 50, // 50 GB storage
488
+ packagesStorageGb: 50, // 50 GB packages
489
+ packagesBandwidthGb: 100, // 100 GB bandwidth
490
+ lfsStorageGb: 250, // 250 GiB storage
491
+ lfsBandwidthGb: 250, // 250 GiB bandwidth/month
492
+ },
493
+ } as const;
494
+
495
+ /**
496
+ * GitHub pricing for overages and paid features
497
+ * Prices are in USD per unit
498
+ *
499
+ * @see https://docs.github.com/en/billing/managing-billing-for-your-products/managing-billing-for-github-actions/about-billing-for-github-actions
500
+ */
501
+ export const GITHUB_PRICING = {
502
+ /** Actions minute overage pricing (per minute, by runner type)
503
+ * Updated Jan 2026: GitHub reduced Actions pricing by up to 39%
504
+ * @see https://docs.github.com/en/billing/reference/actions-runner-pricing */
505
+ actions: {
506
+ linux: 0.006, // $0.006/minute for Linux (was $0.008)
507
+ macos: 0.062, // $0.062/minute for macOS (was $0.08)
508
+ windows: 0.01, // $0.010/minute for Windows (was $0.016)
509
+ /** Larger runners have multiplied pricing */
510
+ linuxLarge: 0.012, // 2x Linux for 4-core
511
+ linuxXLarge: 0.024, // 4x Linux for 8-core
512
+ gpuLinux: 0.07, // GPU-powered runners
513
+ },
514
+ /** Actions storage overage (per GB/month) */
515
+ actionsStorageGb: 0.25, // $0.25/GB/month
516
+ /** Packages storage overage (per GB/month) */
517
+ packagesStorageGb: 0.25, // $0.25/GB/month
518
+ /** Packages bandwidth overage (per GB) */
519
+ packagesBandwidthGb: 0.5, // $0.50/GB
520
+ /** Git LFS storage overage (per GiB/month) */
521
+ lfsStorageGb: 0.07, // $0.07/GiB/month
522
+ /** Git LFS bandwidth overage (per GiB) */
523
+ lfsBandwidthGb: 0.007, // $0.007/GiB
524
+ /** GitHub Enterprise Cloud (per user/month) */
525
+ ghecPerUser: 21, // $21/user/month
526
+ /** GitHub Advanced Security - Code Security (per active committer/month) */
527
+ ghasCodeSecurity: 49, // $49/active committer/month
528
+ /** GitHub Advanced Security - Secret Protection (per active committer/month) */
529
+ ghasSecretProtection: 31, // $31/active committer/month
530
+ /** GitHub Copilot Business (per user/month) */
531
+ copilotBusiness: 19, // $19/user/month
532
+ /** GitHub Copilot Enterprise (per user/month) */
533
+ copilotEnterprise: 39, // $39/user/month
534
+ } as const;
535
+
536
+ /**
537
+ * Get GitHub plan allowances by plan name
538
+ */
539
+ export function getGitHubPlanAllowances(
540
+ planName: string
541
+ ): (typeof GITHUB_ALLOWANCES)[keyof typeof GITHUB_ALLOWANCES] {
542
+ const lowerPlan = planName.toLowerCase();
543
+ if (lowerPlan.includes('enterprise')) return GITHUB_ALLOWANCES.enterprise;
544
+ if (lowerPlan.includes('team')) return GITHUB_ALLOWANCES.team;
545
+ if (lowerPlan.includes('pro')) return GITHUB_ALLOWANCES.pro;
546
+ return GITHUB_ALLOWANCES.free;
547
+ }