@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,365 @@
1
+ ---
2
+ import DashboardLayout from '../layouts/DashboardLayout.astro';
3
+
4
+ interface FeedbackEvent {
5
+ feedbackId: string;
6
+ repo: string;
7
+ issueNumber: number;
8
+ category: string;
9
+ sentiment: string | null;
10
+ relatedErrorCorrelation: string | null;
11
+ correlationConfidence: number;
12
+ correlationMethod: 'rule-based' | 'ml' | 'manual';
13
+ userContactEncrypted: string | null;
14
+ receivedAt: string;
15
+ updatedAt: string | null;
16
+ }
17
+
18
+ const runtime = (Astro.locals as App.Locals | undefined)?.runtime;
19
+ const db = runtime?.env?.PLATFORM_DB;
20
+ const kv = runtime?.env?.PLATFORM_CACHE;
21
+
22
+ let recentFeedback: FeedbackEvent[] = [];
23
+ let correlatedFeedback: FeedbackEvent[] = [];
24
+ let feedbackByCategory: Record<string, number> = {
25
+ 'feature-request': 0,
26
+ 'bug-report': 0,
27
+ 'ux-improvement': 0,
28
+ other: 0,
29
+ };
30
+ let totalFeedback = 0;
31
+
32
+ if (db && kv) {
33
+ // Try KV cache first for recent feedback
34
+ const cachedRecent = await kv.get('feedback:recent:all');
35
+ if (cachedRecent) {
36
+ recentFeedback = JSON.parse(cachedRecent);
37
+ } else {
38
+ // Cache miss - query D1
39
+ const result = await db
40
+ .prepare(
41
+ `SELECT
42
+ feedback_id as feedbackId,
43
+ repo,
44
+ issue_number as issueNumber,
45
+ category,
46
+ sentiment,
47
+ related_error_correlation as relatedErrorCorrelation,
48
+ correlation_confidence as correlationConfidence,
49
+ correlation_method as correlationMethod,
50
+ user_contact_encrypted as userContactEncrypted,
51
+ received_at as receivedAt,
52
+ updated_at as updatedAt
53
+ FROM feedback_events
54
+ ORDER BY received_at DESC
55
+ LIMIT 20`
56
+ )
57
+ .all<FeedbackEvent>();
58
+
59
+ recentFeedback = result.results ?? [];
60
+
61
+ // Cache for 15 minutes
62
+ await kv.put('feedback:recent:all', JSON.stringify(recentFeedback), {
63
+ expirationTtl: 900,
64
+ });
65
+ }
66
+
67
+ // Get correlated feedback (linked to errors)
68
+ const cachedCorrelated = await kv.get('feedback:correlated:all');
69
+ if (cachedCorrelated) {
70
+ correlatedFeedback = JSON.parse(cachedCorrelated);
71
+ } else {
72
+ const result = await db
73
+ .prepare(
74
+ `SELECT
75
+ feedback_id as feedbackId,
76
+ repo,
77
+ issue_number as issueNumber,
78
+ category,
79
+ sentiment,
80
+ related_error_correlation as relatedErrorCorrelation,
81
+ correlation_confidence as correlationConfidence,
82
+ correlation_method as correlationMethod,
83
+ user_contact_encrypted as userContactEncrypted,
84
+ received_at as receivedAt,
85
+ updated_at as updatedAt
86
+ FROM feedback_events
87
+ WHERE related_error_correlation IS NOT NULL
88
+ ORDER BY correlation_confidence DESC, received_at DESC
89
+ LIMIT 20`
90
+ )
91
+ .all<FeedbackEvent>();
92
+
93
+ correlatedFeedback = result.results ?? [];
94
+
95
+ await kv.put('feedback:correlated:all', JSON.stringify(correlatedFeedback), {
96
+ expirationTtl: 900,
97
+ });
98
+ }
99
+
100
+ // Get counts by category
101
+ const categoryResult = await db
102
+ .prepare(
103
+ `SELECT category, COUNT(*) as count
104
+ FROM feedback_events
105
+ GROUP BY category`
106
+ )
107
+ .all<{ category: string; count: number }>();
108
+
109
+ for (const row of categoryResult.results ?? []) {
110
+ feedbackByCategory[row.category] = row.count;
111
+ totalFeedback += row.count;
112
+ }
113
+ }
114
+
115
+ // Calculate priority score for each feedback event
116
+ // Higher correlation confidence = higher priority
117
+ function getPriorityScore(feedback: FeedbackEvent): number {
118
+ let score = 0;
119
+
120
+ // Correlation confidence (0-100 points)
121
+ score += feedback.correlationConfidence * 100;
122
+
123
+ // Category weight
124
+ if (feedback.category === 'bug-report') score += 30;
125
+ if (feedback.category === 'feature-request') score += 20;
126
+ if (feedback.category === 'ux-improvement') score += 15;
127
+
128
+ // Sentiment weight (if available)
129
+ if (feedback.sentiment === 'negative') score += 25;
130
+ if (feedback.sentiment === 'positive') score += 5;
131
+
132
+ return Math.round(score);
133
+ }
134
+
135
+ // Sort by priority
136
+ const prioritizedFeedback = [...recentFeedback].sort(
137
+ (a, b) => getPriorityScore(b) - getPriorityScore(a)
138
+ );
139
+
140
+ function formatDate(dateString: string): string {
141
+ const date = new Date(dateString);
142
+ const now = new Date();
143
+ const diffMs = now.getTime() - date.getTime();
144
+ const diffMins = Math.floor(diffMs / 60000);
145
+ const diffHours = Math.floor(diffMs / 3600000);
146
+ const diffDays = Math.floor(diffMs / 86400000);
147
+
148
+ if (diffMins < 60) return `${diffMins}m ago`;
149
+ if (diffHours < 24) return `${diffHours}h ago`;
150
+ if (diffDays < 7) return `${diffDays}d ago`;
151
+ return date.toLocaleDateString();
152
+ }
153
+
154
+ function getCategoryBadgeColor(category: string): string {
155
+ switch (category) {
156
+ case 'feature-request':
157
+ return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300';
158
+ case 'bug-report':
159
+ return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
160
+ case 'ux-improvement':
161
+ return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300';
162
+ default:
163
+ return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300';
164
+ }
165
+ }
166
+
167
+ function getPriorityBadgeColor(score: number): string {
168
+ if (score >= 120) return 'bg-red-500 text-white';
169
+ if (score >= 80) return 'bg-orange-500 text-white';
170
+ if (score >= 40) return 'bg-yellow-500 text-white';
171
+ return 'bg-green-500 text-white';
172
+ }
173
+ ---
174
+
175
+ <DashboardLayout title="Feedback">
176
+ <div class="space-y-6">
177
+ <h2 class="text-3xl font-bold text-gray-900 dark:text-white">User Feedback</h2>
178
+
179
+ <!-- Summary Cards -->
180
+ <div class="grid grid-cols-1 gap-6 md:grid-cols-4">
181
+ <div class="metric-card">
182
+ <div class="metric-title">Total Feedback</div>
183
+ <div class="metric-value">{totalFeedback}</div>
184
+ <div class="mt-2 text-sm text-gray-600 dark:text-gray-400">All time</div>
185
+ </div>
186
+
187
+ <div class="metric-card">
188
+ <div class="metric-title">Feature Requests</div>
189
+ <div class="metric-value">{feedbackByCategory['feature-request']}</div>
190
+ <div class="mt-2 text-sm text-gray-600 dark:text-gray-400">
191
+ {((feedbackByCategory['feature-request'] / totalFeedback) * 100).toFixed(0)}%
192
+ </div>
193
+ </div>
194
+
195
+ <div class="metric-card">
196
+ <div class="metric-title">Bug Reports</div>
197
+ <div class="metric-value">{feedbackByCategory['bug-report']}</div>
198
+ <div class="mt-2 text-sm text-gray-600 dark:text-gray-400">
199
+ {((feedbackByCategory['bug-report'] / totalFeedback) * 100).toFixed(0)}%
200
+ </div>
201
+ </div>
202
+
203
+ <div class="metric-card">
204
+ <div class="metric-title">Correlated</div>
205
+ <div class="metric-value">{correlatedFeedback.length}</div>
206
+ <div class="mt-2 text-sm text-gray-600 dark:text-gray-400">Linked to errors</div>
207
+ </div>
208
+ </div>
209
+
210
+ <!-- Prioritized Feedback List -->
211
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
212
+ <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Feedback by Priority</h3>
213
+
214
+ <div class="space-y-3">
215
+ {
216
+ prioritizedFeedback.length === 0 ? (
217
+ <p class="text-gray-600 dark:text-gray-400">No feedback events yet.</p>
218
+ ) : (
219
+ prioritizedFeedback.map((feedback) => {
220
+ const priorityScore = getPriorityScore(feedback);
221
+ return (
222
+ <div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
223
+ <div class="flex items-start justify-between">
224
+ <div class="flex-1">
225
+ <div class="flex items-center gap-2 mb-2">
226
+ <span
227
+ class={`px-2 py-1 rounded text-xs font-medium ${getCategoryBadgeColor(feedback.category)}`}
228
+ >
229
+ {feedback.category}
230
+ </span>
231
+ <span
232
+ class={`px-2 py-1 rounded text-xs font-bold ${getPriorityBadgeColor(priorityScore)}`}
233
+ >
234
+ P{priorityScore}
235
+ </span>
236
+ {feedback.relatedErrorCorrelation && (
237
+ <span class="px-2 py-1 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">
238
+ Correlated ({Math.round(feedback.correlationConfidence * 100)}%)
239
+ </span>
240
+ )}
241
+ </div>
242
+
243
+ <div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
244
+ <span class="font-mono">{feedback.repo}</span>
245
+ <span>•</span>
246
+ <a
247
+ href={`https://github.com/littlebearapps/${feedback.repo}/issues/${feedback.issueNumber}`}
248
+ target="_blank"
249
+ rel="noopener noreferrer"
250
+ class="text-blue-600 dark:text-blue-400 hover:underline"
251
+ >
252
+ #{feedback.issueNumber}
253
+ </a>
254
+ <span>•</span>
255
+ <span>{formatDate(feedback.receivedAt)}</span>
256
+ </div>
257
+
258
+ {feedback.relatedErrorCorrelation && (
259
+ <div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
260
+ <span>Linked to error: </span>
261
+ <span class="font-mono">
262
+ {feedback.relatedErrorCorrelation.substring(0, 8)}...
263
+ </span>
264
+ <span class="ml-2">via {feedback.correlationMethod}</span>
265
+ </div>
266
+ )}
267
+
268
+ {feedback.userContactEncrypted && (
269
+ <div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
270
+ <span>User contact provided (encrypted)</span>
271
+ </div>
272
+ )}
273
+ </div>
274
+ </div>
275
+ </div>
276
+ );
277
+ })
278
+ )
279
+ }
280
+ </div>
281
+ </div>
282
+
283
+ <!-- Correlated Feedback Section -->
284
+ {
285
+ correlatedFeedback.length > 0 && (
286
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
287
+ <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
288
+ High-Confidence Correlations
289
+ </h3>
290
+
291
+ <div class="space-y-3">
292
+ {correlatedFeedback.map((feedback) => (
293
+ <div class="border border-green-200 dark:border-green-700 rounded-lg p-4 bg-green-50 dark:bg-green-900/20">
294
+ <div class="flex items-start justify-between">
295
+ <div class="flex-1">
296
+ <div class="flex items-center gap-2 mb-2">
297
+ <span
298
+ class={`px-2 py-1 rounded text-xs font-medium ${getCategoryBadgeColor(feedback.category)}`}
299
+ >
300
+ {feedback.category}
301
+ </span>
302
+ <span class="px-2 py-1 rounded text-xs font-bold bg-green-600 text-white">
303
+ {Math.round(feedback.correlationConfidence * 100)}% confident
304
+ </span>
305
+ </div>
306
+
307
+ <div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
308
+ <span class="font-mono">{feedback.repo}</span>
309
+ <span>•</span>
310
+ <a
311
+ href={`https://github.com/littlebearapps/${feedback.repo}/issues/${feedback.issueNumber}`}
312
+ target="_blank"
313
+ rel="noopener noreferrer"
314
+ class="text-blue-600 dark:text-blue-400 hover:underline"
315
+ >
316
+ #{feedback.issueNumber}
317
+ </a>
318
+ <span>•</span>
319
+ <span>{formatDate(feedback.receivedAt)}</span>
320
+ </div>
321
+
322
+ <div class="mt-2 text-sm text-gray-700 dark:text-gray-300">
323
+ <span>Linked to error: </span>
324
+ <span class="font-mono bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
325
+ {feedback.relatedErrorCorrelation}
326
+ </span>
327
+ <span class="ml-2 text-xs text-gray-500 dark:text-gray-400">
328
+ (via {feedback.correlationMethod})
329
+ </span>
330
+ </div>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ ))}
335
+ </div>
336
+ </div>
337
+ )
338
+ }
339
+
340
+ <!-- Category Breakdown -->
341
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
342
+ <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Feedback by Category</h3>
343
+
344
+ <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
345
+ {
346
+ Object.entries(feedbackByCategory).map(([category, count]) => (
347
+ <div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
348
+ <div class="flex items-center justify-between">
349
+ <span
350
+ class={`px-3 py-1 rounded text-sm font-medium ${getCategoryBadgeColor(category)}`}
351
+ >
352
+ {category}
353
+ </span>
354
+ <span class="text-2xl font-bold text-gray-900 dark:text-white">{count}</span>
355
+ </div>
356
+ <div class="mt-2 text-sm text-gray-600 dark:text-gray-400">
357
+ {totalFeedback > 0 ? ((count / totalFeedback) * 100).toFixed(1) : 0}% of total
358
+ </div>
359
+ </div>
360
+ ))
361
+ }
362
+ </div>
363
+ </div>
364
+ </div>
365
+ </DashboardLayout>
@@ -0,0 +1,206 @@
1
+ ---
2
+ const runtime = (Astro.locals as App.Locals | undefined)?.runtime;
3
+ const db = runtime?.env?.PLATFORM_DB;
4
+
5
+ let errors24h = 0;
6
+ let mrr = 0;
7
+ let activeUsers = 0;
8
+ let alerts = 0;
9
+
10
+ if (db) {
11
+ // Wrap each query in try/catch - tables may not exist yet
12
+ try {
13
+ const errorRow = await db
14
+ .prepare(
15
+ `SELECT COUNT(*) AS count FROM error_reports
16
+ WHERE timestamp > unixepoch() - 86400`
17
+ )
18
+ .first<{ count: number }>();
19
+ errors24h = errorRow?.count ?? 0;
20
+ } catch {
21
+ // Table doesn't exist yet
22
+ }
23
+
24
+ try {
25
+ const mrrRow = await db
26
+ .prepare(
27
+ `SELECT value FROM revenue_metrics
28
+ WHERE metric_type = 'mrr'
29
+ ORDER BY timestamp DESC
30
+ LIMIT 1`
31
+ )
32
+ .first<{ value: number }>();
33
+ mrr = mrrRow?.value ?? 0;
34
+ } catch {
35
+ // Table doesn't exist yet
36
+ }
37
+
38
+ try {
39
+ const activeUsersRow = await db
40
+ .prepare(
41
+ `SELECT value FROM product_metrics
42
+ WHERE metric_type = 'active_users'
43
+ ORDER BY timestamp DESC
44
+ LIMIT 1`
45
+ )
46
+ .first<{ value: number }>();
47
+ activeUsers = activeUsersRow?.value ?? 0;
48
+ } catch {
49
+ // Table doesn't exist yet
50
+ }
51
+
52
+ try {
53
+ const alertsRow = await db
54
+ .prepare(
55
+ `SELECT COUNT(*) AS count FROM alerts
56
+ WHERE resolved = 0 AND severity IN ('critical', 'high')`
57
+ )
58
+ .first<{ count: number }>();
59
+ alerts = alertsRow?.count ?? 0;
60
+ } catch {
61
+ // Table doesn't exist yet
62
+ }
63
+ }
64
+ ---
65
+
66
+ <!doctype html>
67
+ <html lang="en">
68
+ <head>
69
+ <meta charset="UTF-8" />
70
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
71
+ <title>Platform Kiosk</title>
72
+ <style>
73
+ body {
74
+ margin: 0;
75
+ padding: 2rem;
76
+ background: #111;
77
+ color: #fff;
78
+ font-family:
79
+ 'Inter',
80
+ system-ui,
81
+ -apple-system,
82
+ BlinkMacSystemFont,
83
+ 'Segoe UI',
84
+ sans-serif;
85
+ }
86
+
87
+ .kiosk-grid {
88
+ display: grid;
89
+ grid-template-columns: repeat(2, minmax(0, 1fr));
90
+ gap: 2rem;
91
+ min-height: calc(100vh - 4rem);
92
+ }
93
+
94
+ .kiosk-tile {
95
+ background: #222;
96
+ border-radius: 1.25rem;
97
+ padding: 3rem;
98
+ display: flex;
99
+ flex-direction: column;
100
+ justify-content: center;
101
+ align-items: center;
102
+ text-align: center;
103
+ box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.35);
104
+ }
105
+
106
+ .kiosk-label {
107
+ font-size: 2rem;
108
+ text-transform: uppercase;
109
+ letter-spacing: 0.08em;
110
+ opacity: 0.7;
111
+ margin-bottom: 1.5rem;
112
+ }
113
+
114
+ .kiosk-value {
115
+ font-size: clamp(4rem, 8vw, 7rem);
116
+ font-weight: 700;
117
+ line-height: 1;
118
+ }
119
+
120
+ .kiosk-unit {
121
+ font-size: 2rem;
122
+ opacity: 0.6;
123
+ margin-top: 1rem;
124
+ }
125
+
126
+ .status-ok {
127
+ color: #10b981;
128
+ }
129
+
130
+ .status-warning {
131
+ color: #f59e0b;
132
+ }
133
+
134
+ .status-error {
135
+ color: #ef4444;
136
+ }
137
+
138
+ .refresh-indicator {
139
+ position: fixed;
140
+ bottom: 2rem;
141
+ right: 2rem;
142
+ opacity: 0.5;
143
+ font-size: 1rem;
144
+ }
145
+
146
+ @media (max-width: 900px) {
147
+ body {
148
+ padding: 1.5rem;
149
+ }
150
+
151
+ .kiosk-grid {
152
+ grid-template-columns: 1fr;
153
+ gap: 1.5rem;
154
+ }
155
+
156
+ .kiosk-tile {
157
+ padding: 2rem;
158
+ }
159
+
160
+ .kiosk-label {
161
+ font-size: 1.5rem;
162
+ }
163
+ }
164
+ </style>
165
+ </head>
166
+ <body>
167
+ <div class="kiosk-grid">
168
+ <div class="kiosk-tile">
169
+ <div class="kiosk-label">Errors (24h)</div>
170
+ <div class={`kiosk-value ${errors24h > 10 ? 'status-error' : 'status-ok'}`}>
171
+ {errors24h}
172
+ </div>
173
+ </div>
174
+
175
+ <div class="kiosk-tile">
176
+ <div class="kiosk-label">MRR</div>
177
+ <div class="kiosk-value status-ok">
178
+ ${mrr.toFixed(0)}
179
+ </div>
180
+ </div>
181
+
182
+ <div class="kiosk-tile">
183
+ <div class="kiosk-label">Active Users</div>
184
+ <div class="kiosk-value status-ok">
185
+ {activeUsers.toLocaleString()}
186
+ </div>
187
+ <div class="kiosk-unit">Last 30 Days</div>
188
+ </div>
189
+
190
+ <div class="kiosk-tile">
191
+ <div class="kiosk-label">Active Alerts</div>
192
+ <div class={`kiosk-value ${alerts > 0 ? 'status-error' : 'status-ok'}`}>
193
+ {alerts}
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ <div class="refresh-indicator">Auto-refresh: 60s</div>
199
+
200
+ <script>
201
+ setTimeout(() => {
202
+ window.location.reload();
203
+ }, 60000);
204
+ </script>
205
+ </body>
206
+ </html>