@lunora/studio 0.0.0 → 1.0.0-alpha.1

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 (81) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +123 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.ts +1402 -0
  5. package/dist/index.js +41 -0
  6. package/dist/mount.d.ts +21 -0
  7. package/dist/mount.js +26 -0
  8. package/dist/packem_shared/ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js +45 -0
  9. package/dist/packem_shared/ApiDocsPanel-DpRjJhG5.js +842 -0
  10. package/dist/packem_shared/ApiReferencePanel-DMIUp-kK.js +229 -0
  11. package/dist/packem_shared/ApiTab-DURGU15e.js +251 -0
  12. package/dist/packem_shared/AuditPanel-BC59Nhst.js +212 -0
  13. package/dist/packem_shared/CommandPalette-Dx_CoB9i.js +373 -0
  14. package/dist/packem_shared/ConfirmButton-WQVUoGFb.js +59 -0
  15. package/dist/packem_shared/ConnectionBadge-Bxagrip8.js +111 -0
  16. package/dist/packem_shared/DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js +50 -0
  17. package/dist/packem_shared/DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js +89 -0
  18. package/dist/packem_shared/DataBrowser-Coz6jJE6.js +4542 -0
  19. package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
  20. package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
  21. package/dist/packem_shared/ExportImportPanel-WO34fJxy.js +193 -0
  22. package/dist/packem_shared/FileBrowser-Zcr-Qgxo.js +2932 -0
  23. package/dist/packem_shared/FunctionRunner-j0Rd5m9t.js +343 -0
  24. package/dist/packem_shared/FunctionStatsPanel-DboBl-XL.js +432 -0
  25. package/dist/packem_shared/GlobalDataBrowser-9MhPEfgN.js +318 -0
  26. package/dist/packem_shared/HealthPanel-DOIgbUtx.js +640 -0
  27. package/dist/packem_shared/HomePanel-bdOCNA-p.js +1273 -0
  28. package/dist/packem_shared/InsightsPanel-DaZPnSgt.js +423 -0
  29. package/dist/packem_shared/LogsPanel-CWdqAGpQ.js +839 -0
  30. package/dist/packem_shared/MailPanel-D_EGtDnS.js +447 -0
  31. package/dist/packem_shared/MetricsPanel-E4Gv6wTO.js +1625 -0
  32. package/dist/packem_shared/MigrationsPanel-DQdPY9io.js +246 -0
  33. package/dist/packem_shared/OpenRpcReferencePanel-j2p3HB0s.js +191 -0
  34. package/dist/packem_shared/PitrPanel-BbBkQR6t.js +252 -0
  35. package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
  36. package/dist/packem_shared/ScheduledJobs-Ok1CYYwI.js +159 -0
  37. package/dist/packem_shared/SchemaViewer-D8XGnp-X.js +2512 -0
  38. package/dist/packem_shared/SecurityAdvisorPanel-Cdm2IxLW.js +79 -0
  39. package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +176 -0
  40. package/dist/packem_shared/ShardInput-DNCsT1KW.js +107 -0
  41. package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +13 -0
  42. package/dist/packem_shared/Studio-D36od9Oz.js +33 -0
  43. package/dist/packem_shared/StudioApp-dvywkJ8I.js +383 -0
  44. package/dist/packem_shared/StudioI18nProvider-Dcajsznk.js +48 -0
  45. package/dist/packem_shared/TableEditor-DIVDk3vT.js +371 -0
  46. package/dist/packem_shared/advisor-view-DBlzJi6C.js +159 -0
  47. package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
  48. package/dist/packem_shared/app.d-CCmwDEVs.d.ts +300 -0
  49. package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
  50. package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
  51. package/dist/packem_shared/button-BhsN2uZH.js +49 -0
  52. package/dist/packem_shared/card-DURq3ElK.js +175 -0
  53. package/dist/packem_shared/cf-links-BZfRdxSE.js +8 -0
  54. package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
  55. package/dist/packem_shared/createStudioI18n-CgvlmDkN.js +27 -0
  56. package/dist/packem_shared/data-grid-CCh2Couo.js +183 -0
  57. package/dist/packem_shared/dropdown-menu-WY4B_eJO.js +280 -0
  58. package/dist/packem_shared/empty-state-DY_oe0k6.js +98 -0
  59. package/dist/packem_shared/grid-features-DTjG6Sex.js +840 -0
  60. package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
  61. package/dist/packem_shared/internal-BBZYexre.js +68 -0
  62. package/dist/packem_shared/label-D8ykjn5J.js +46 -0
  63. package/dist/packem_shared/live-status-bPff1O7Y.js +44 -0
  64. package/dist/packem_shared/reference-view-BCKIoai7.js +2180 -0
  65. package/dist/packem_shared/shard-history-DyebH1R5.js +38 -0
  66. package/dist/packem_shared/sparkline-10dG-_f0.js +93 -0
  67. package/dist/packem_shared/sql-editor-panel-CW2y2x9h.js +2562 -0
  68. package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
  69. package/dist/packem_shared/studio-BDVd7rIV.js +10303 -0
  70. package/dist/packem_shared/table-_RzNvy3R.js +246 -0
  71. package/dist/packem_shared/table-list-sidebar-aZHLq70w.js +832 -0
  72. package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
  73. package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +73 -0
  74. package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +76 -0
  75. package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
  76. package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
  77. package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
  78. package/dist/standalone/studio.js +356 -0
  79. package/dist/styles.css +2 -0
  80. package/package.json +77 -17
  81. package/src/theme.css +59 -0
@@ -0,0 +1,640 @@
1
+ import { c } from 'react/compiler-runtime';
2
+ import { useLunora } from '@lunora/react';
3
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
4
+ import ConnectionBadge from './ConnectionBadge-Bxagrip8.js';
5
+ import { L as LiveError } from './live-status-bPff1O7Y.js';
6
+ import { B as Badge } from './badge-B2PKA1-5.js';
7
+ import { C as Card } from './card-DURq3ElK.js';
8
+ import { u as useLiveAdmin } from './use-live-admin-D1h1Fzsd.js';
9
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
10
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
11
+ import { f as fireAndForget, d as formatTimestamp, c as callOptions, e as errorMessage, a as adminRef } from './internal-BBZYexre.js';
12
+ import { l as loadRecentShards } from './shard-history-DyebH1R5.js';
13
+ import { c as cn } from './utils-B05Dmz_H.js';
14
+ import { shardsToAggregate } from './aggregateMetrics-D4nUHEKU.js';
15
+ import { S as Sparkline } from './sparkline-10dG-_f0.js';
16
+ import { jsxDEV } from 'react/jsx-dev-runtime';
17
+
18
+ const STATUS_RANK = {
19
+ completed: 0,
20
+ failed: 2,
21
+ in_progress: 1
22
+ };
23
+ const sumShardMetrics = (results) => {
24
+ let requests = 0;
25
+ let errors = 0;
26
+ let reachable = 0;
27
+ let failed = 0;
28
+ const history = [];
29
+ for (const {
30
+ metrics
31
+ } of results) {
32
+ if (metrics === null) {
33
+ failed += 1;
34
+ continue;
35
+ }
36
+ reachable += 1;
37
+ requests += metrics.requests;
38
+ errors += metrics.errors;
39
+ history.push(...metrics.history ?? []);
40
+ }
41
+ return {
42
+ errors,
43
+ failed,
44
+ history,
45
+ reachable,
46
+ requests
47
+ };
48
+ };
49
+ const mergeFunctionStats = (perShard) => {
50
+ const byPath = /* @__PURE__ */ new Map();
51
+ for (const shard of perShard) {
52
+ for (const stat of shard) {
53
+ const existing = byPath.get(stat.path);
54
+ if (existing === void 0) {
55
+ byPath.set(stat.path, {
56
+ ...stat
57
+ });
58
+ continue;
59
+ }
60
+ existing.calls += stat.calls;
61
+ existing.errors += stat.errors;
62
+ existing.totalDurationMs += stat.totalDurationMs;
63
+ existing.scans = (existing.scans ?? 0) + (stat.scans ?? 0);
64
+ existing.maxDurationMs = Math.max(existing.maxDurationMs, stat.maxDurationMs);
65
+ existing.lastCalledAt = Math.max(existing.lastCalledAt, stat.lastCalledAt);
66
+ if ((stat.lastErrorAt ?? 0) > (existing.lastErrorAt ?? 0)) {
67
+ existing.lastErrorAt = stat.lastErrorAt;
68
+ existing.lastErrorMessage = stat.lastErrorMessage;
69
+ }
70
+ }
71
+ }
72
+ return [...byPath.values()];
73
+ };
74
+ const dedupeMigrations = (perShard) => {
75
+ const byId = /* @__PURE__ */ new Map();
76
+ for (const shard of perShard) {
77
+ for (const row of shard) {
78
+ const existing = byId.get(row.id);
79
+ if (existing === void 0) {
80
+ byId.set(row.id, row);
81
+ continue;
82
+ }
83
+ const rankDelta = STATUS_RANK[row.status] - STATUS_RANK[existing.status];
84
+ if (rankDelta > 0 || rankDelta === 0 && (row.updatedAt ?? 0) > (existing.updatedAt ?? 0)) {
85
+ byId.set(row.id, row);
86
+ }
87
+ }
88
+ }
89
+ return [...byId.values()];
90
+ };
91
+
92
+ const GET_AUTH_METRICS = adminRef(ADMIN_FUNCTIONS.getAuthMetrics);
93
+ const GET_FUNCTION_STATS = adminRef(ADMIN_FUNCTIONS.getFunctionStats);
94
+ const GET_LOGS = adminRef(ADMIN_FUNCTIONS.getLogs);
95
+ const GET_METRICS = adminRef(ADMIN_FUNCTIONS.getMetrics);
96
+ const MIGRATION_STATUS = adminRef(ADMIN_FUNCTIONS.migrationStatus);
97
+ const RECENT_ERROR_LIMIT = 5;
98
+ const MIN_FANOUT_INTERVAL_MS = 2e3;
99
+ const TOP_FUNCTION_LIMIT = 5;
100
+ const REQUEST_ERROR_WARN = 0.01;
101
+ const REQUEST_ERROR_CRIT = 0.05;
102
+ const AUTH_FAIL_WARN = 0.1;
103
+ const AUTH_FAIL_CRIT = 0.3;
104
+ const BACKLOG_WARN = 1;
105
+ const BACKLOG_CRIT = 50;
106
+ const rateLevel = (rate, warn, crit) => {
107
+ if (rate >= crit) {
108
+ return "crit";
109
+ }
110
+ return rate >= warn ? "warn" : "ok";
111
+ };
112
+ const countLevel = (count, warn, crit) => {
113
+ if (count >= crit) {
114
+ return "crit";
115
+ }
116
+ return count >= warn ? "warn" : "ok";
117
+ };
118
+ const LEVEL_VARIANT = {
119
+ crit: "destructive",
120
+ ok: "secondary",
121
+ warn: "default"
122
+ };
123
+ const ratePercent = (numerator, denominator) => {
124
+ if (denominator === 0) {
125
+ return "—";
126
+ }
127
+ return `${(numerator / denominator * 100).toFixed(1)}%`;
128
+ };
129
+ const requestErrorSeries = (history) => {
130
+ const byBucket = /* @__PURE__ */ new Map();
131
+ for (const bucket of history ?? []) {
132
+ const slot = byBucket.get(bucket.bucketMs) ?? {
133
+ calls: 0,
134
+ errors: 0
135
+ };
136
+ slot.calls += bucket.calls;
137
+ slot.errors += bucket.errors;
138
+ byBucket.set(bucket.bucketMs, slot);
139
+ }
140
+ const ordered = [...byBucket.entries()].toSorted((a, b) => a[0] - b[0]);
141
+ return {
142
+ errors: ordered.map(([, slot]) => slot.errors),
143
+ requests: ordered.map(([, slot]) => slot.calls)
144
+ };
145
+ };
146
+ const migrationSummary = (rows) => {
147
+ let failed = 0;
148
+ let running = 0;
149
+ for (const row of rows) {
150
+ if (row.status === "failed") {
151
+ failed += 1;
152
+ } else if (row.status === "in_progress") {
153
+ running += 1;
154
+ }
155
+ }
156
+ let level = "ok";
157
+ if (failed > 0) {
158
+ level = "crit";
159
+ } else if (running > 0) {
160
+ level = "warn";
161
+ }
162
+ return {
163
+ failed,
164
+ level,
165
+ running
166
+ };
167
+ };
168
+ const migrationTileValue = (summary, t) => {
169
+ if (summary.failed > 0) {
170
+ return t("{count} failed", {
171
+ count: summary.failed.toString()
172
+ });
173
+ }
174
+ if (summary.running > 0) {
175
+ return t("{count} running", {
176
+ count: summary.running.toString()
177
+ });
178
+ }
179
+ return t("OK");
180
+ };
181
+ const LEVEL_DOT = {
182
+ crit: "bg-destructive",
183
+ ok: "bg-success",
184
+ warn: "bg-warning"
185
+ };
186
+ const LEVEL_TEXT = {
187
+ crit: "text-destructive",
188
+ ok: "text-foreground",
189
+ warn: "text-warning"
190
+ };
191
+ const LEVEL_RING = {
192
+ crit: "bg-destructive/10 text-destructive",
193
+ ok: "bg-success/10 text-success",
194
+ warn: "bg-warning/10 text-warning"
195
+ };
196
+ const worstLevel = (levels) => {
197
+ if (levels.includes("crit")) {
198
+ return "crit";
199
+ }
200
+ return levels.includes("warn") ? "warn" : "ok";
201
+ };
202
+ const SloCard = (t0) => {
203
+ const $ = c(19);
204
+ const {
205
+ chart,
206
+ label,
207
+ level,
208
+ testId,
209
+ value
210
+ } = t0;
211
+ const t1 = LEVEL_DOT[level];
212
+ let t2;
213
+ if ($[0] !== t1) {
214
+ t2 = cn("size-1.5 shrink-0 rounded-full", t1);
215
+ $[0] = t1;
216
+ $[1] = t2;
217
+ } else {
218
+ t2 = $[1];
219
+ }
220
+ let t3;
221
+ if ($[2] !== t2) {
222
+ t3 = /* @__PURE__ */ jsxDEV("span", {
223
+ "aria-hidden": "true",
224
+ className: t2
225
+ }, void 0, false);
226
+ $[2] = t2;
227
+ $[3] = t3;
228
+ } else {
229
+ t3 = $[3];
230
+ }
231
+ let t4;
232
+ if ($[4] !== label || $[5] !== t3) {
233
+ t4 = /* @__PURE__ */ jsxDEV("span", {
234
+ className: "flex items-center gap-1.5 font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
235
+ children: [t3, label]
236
+ }, void 0, true);
237
+ $[4] = label;
238
+ $[5] = t3;
239
+ $[6] = t4;
240
+ } else {
241
+ t4 = $[6];
242
+ }
243
+ const t5 = LEVEL_TEXT[level];
244
+ let t6;
245
+ if ($[7] !== t5) {
246
+ t6 = cn("truncate text-2xl font-semibold tabular-nums", t5);
247
+ $[7] = t5;
248
+ $[8] = t6;
249
+ } else {
250
+ t6 = $[8];
251
+ }
252
+ let t7;
253
+ if ($[9] !== t6 || $[10] !== testId || $[11] !== value) {
254
+ t7 = /* @__PURE__ */ jsxDEV("span", {
255
+ className: t6,
256
+ "data-testid": testId,
257
+ children: value
258
+ }, void 0, false);
259
+ $[9] = t6;
260
+ $[10] = testId;
261
+ $[11] = value;
262
+ $[12] = t7;
263
+ } else {
264
+ t7 = $[12];
265
+ }
266
+ let t8;
267
+ if ($[13] !== chart || $[14] !== t7) {
268
+ t8 = /* @__PURE__ */ jsxDEV("div", {
269
+ className: "flex items-center justify-between gap-3",
270
+ children: [t7, chart]
271
+ }, void 0, true);
272
+ $[13] = chart;
273
+ $[14] = t7;
274
+ $[15] = t8;
275
+ } else {
276
+ t8 = $[15];
277
+ }
278
+ let t9;
279
+ if ($[16] !== t4 || $[17] !== t8) {
280
+ t9 = /* @__PURE__ */ jsxDEV(Card, {
281
+ className: "gap-0 py-0",
282
+ children: /* @__PURE__ */ jsxDEV("div", {
283
+ className: "flex flex-col gap-2.5 p-4",
284
+ children: [t4, t8]
285
+ }, void 0, true)
286
+ }, void 0, false);
287
+ $[16] = t4;
288
+ $[17] = t8;
289
+ $[18] = t9;
290
+ } else {
291
+ t9 = $[18];
292
+ }
293
+ return t9;
294
+ };
295
+ const loadSloData = async (client, rootShard, shards) => {
296
+ const perShardSettled = await Promise.all(shards.map((shard) => {
297
+ const shardOptions = callOptions(shard);
298
+ return Promise.allSettled([client.query(GET_METRICS, {}, shardOptions), client.query(GET_FUNCTION_STATS, {}, shardOptions), client.query(MIGRATION_STATUS, {}, shardOptions)]);
299
+ }));
300
+ const rootOptions = callOptions(rootShard);
301
+ const schedulerStatus = typeof client.schedulerStatus === "function" ? client.schedulerStatus() : Promise.reject(new Error("scheduler status unavailable"));
302
+ const [logs, authMetrics, schedulerState] = await Promise.allSettled([client.query(GET_LOGS, {}, rootOptions), client.query(GET_AUTH_METRICS, {}, rootOptions), schedulerStatus]);
303
+ const perShard = perShardSettled.map(([m, f, mig]) => {
304
+ return {
305
+ functions: f.status === "fulfilled" ? f.value.functions : [],
306
+ metrics: m.status === "fulfilled" ? m.value : null,
307
+ migrations: mig.status === "fulfilled" ? mig.value.migrations : []
308
+ };
309
+ });
310
+ const totals = sumShardMetrics(perShard);
311
+ const rootMetrics = perShardSettled[0]?.[0];
312
+ return {
313
+ auth: authMetrics.status === "fulfilled" ? authMetrics.value : null,
314
+ entries: logs.status === "fulfilled" ? logs.value.entries : [],
315
+ functions: mergeFunctionStats(perShard.map((shardResult) => shardResult.functions)),
316
+ logsError: logs.status === "rejected" ? errorMessage(logs.reason) : null,
317
+ metricsError: totals.reachable === 0 && rootMetrics?.status === "rejected" ? errorMessage(rootMetrics.reason) : null,
318
+ migrations: dedupeMigrations(perShard.map((shardResult) => shardResult.migrations)),
319
+ scheduler: schedulerState.status === "fulfilled" ? schedulerState.value : null,
320
+ totals
321
+ };
322
+ };
323
+ const HealthPanel = ({
324
+ initialShardKey
325
+ }) => {
326
+ const client = useLunora();
327
+ const t = useT();
328
+ const [entries, setEntries] = useState([]);
329
+ const [logsError, setLogsError] = useState(null);
330
+ const [totals, setTotals] = useState(null);
331
+ const [metricsError, setMetricsError] = useState(null);
332
+ const [functions, setFunctions] = useState([]);
333
+ const [auth, setAuth] = useState(null);
334
+ const [scheduler, setScheduler] = useState(null);
335
+ const [migrations, setMigrations] = useState([]);
336
+ const [liveError, setLiveError] = useState(void 0);
337
+ const [recentShards] = useState(loadRecentShards);
338
+ const inFlightRef = useRef(false);
339
+ const mountedRef = useRef(true);
340
+ useEffect(() => {
341
+ mountedRef.current = true;
342
+ return () => {
343
+ mountedRef.current = false;
344
+ };
345
+ }, []);
346
+ const rootShard = initialShardKey ?? "";
347
+ const refresh = useCallback(async () => {
348
+ if (inFlightRef.current) {
349
+ return;
350
+ }
351
+ inFlightRef.current = true;
352
+ const result = await loadSloData(client, rootShard, shardsToAggregate(rootShard, recentShards));
353
+ if (!mountedRef.current) {
354
+ inFlightRef.current = false;
355
+ return;
356
+ }
357
+ setTotals(result.totals);
358
+ setMetricsError(result.metricsError);
359
+ setFunctions(result.functions);
360
+ setMigrations(result.migrations);
361
+ setEntries(result.entries);
362
+ setLogsError(result.logsError);
363
+ setAuth(result.auth);
364
+ setScheduler(result.scheduler);
365
+ inFlightRef.current = false;
366
+ }, [client, recentShards, rootShard]);
367
+ useEffect(() => {
368
+ fireAndForget(refresh());
369
+ }, [refresh]);
370
+ const lastFanOutRef = useRef(0);
371
+ const trailingTimerRef = useRef(null);
372
+ const scheduleFanOut = () => {
373
+ const elapsed = Date.now() - lastFanOutRef.current;
374
+ if (elapsed >= MIN_FANOUT_INTERVAL_MS) {
375
+ lastFanOutRef.current = Date.now();
376
+ fireAndForget(refresh());
377
+ return;
378
+ }
379
+ trailingTimerRef.current ??= setTimeout(() => {
380
+ trailingTimerRef.current = null;
381
+ lastFanOutRef.current = Date.now();
382
+ fireAndForget(refresh());
383
+ }, MIN_FANOUT_INTERVAL_MS - elapsed);
384
+ };
385
+ useEffect(() => () => {
386
+ if (trailingTimerRef.current !== null) {
387
+ clearTimeout(trailingTimerRef.current);
388
+ }
389
+ }, []);
390
+ useLiveAdmin(ADMIN_FUNCTIONS.getMetrics, {}, rootShard, () => {
391
+ setLiveError(void 0);
392
+ scheduleFanOut();
393
+ }, true, setLiveError);
394
+ const recentErrors = entries.filter((entry) => entry.level === "error");
395
+ const topErrors = recentErrors.slice(0, RECENT_ERROR_LIMIT);
396
+ const trend = requestErrorSeries(totals?.history);
397
+ const authTrend = useMemo(() => {
398
+ const buckets = auth?.history ?? [];
399
+ return {
400
+ attempts: buckets.map((bucket) => bucket.attempts),
401
+ failures: buckets.map((bucket_0) => bucket_0.failures)
402
+ };
403
+ }, [auth?.history]);
404
+ const worstFunctions = functions.filter((stat) => stat.calls > 0).toSorted((a, b) => b.errors / b.calls - a.errors / a.calls || b.calls - a.calls).slice(0, TOP_FUNCTION_LIMIT);
405
+ const migration = migrationSummary(migrations);
406
+ const appErrorRate = totals === null || totals.requests === 0 ? 0 : totals.errors / totals.requests;
407
+ const errorLevel = rateLevel(appErrorRate, REQUEST_ERROR_WARN, REQUEST_ERROR_CRIT);
408
+ const authLevel = auth === null ? "ok" : rateLevel(auth.failureRate, AUTH_FAIL_WARN, AUTH_FAIL_CRIT);
409
+ const backlogLevel = scheduler === null ? "ok" : countLevel(scheduler.backlog, BACKLOG_WARN, BACKLOG_CRIT);
410
+ const overall = worstLevel([errorLevel, authLevel, backlogLevel, migration.level]);
411
+ let statusLabel;
412
+ let statusDescription;
413
+ if (overall === "crit") {
414
+ statusLabel = t("Critical");
415
+ statusDescription = t("One or more service levels are breached.");
416
+ } else if (overall === "warn") {
417
+ statusLabel = t("Degraded");
418
+ statusDescription = t("Some service levels need attention.");
419
+ } else {
420
+ statusLabel = t("All systems healthy");
421
+ statusDescription = t("All service levels are within target.");
422
+ }
423
+ return /* @__PURE__ */ jsxDEV("div", {
424
+ className: "flex flex-col gap-6",
425
+ "data-testid": "lunora-health",
426
+ children: [/* @__PURE__ */ jsxDEV(Card, {
427
+ className: "gap-0 py-0",
428
+ children: [/* @__PURE__ */ jsxDEV("div", {
429
+ className: "flex flex-wrap items-center justify-between gap-4 p-4",
430
+ children: [/* @__PURE__ */ jsxDEV("div", {
431
+ className: "flex items-center gap-3",
432
+ children: [/* @__PURE__ */ jsxDEV("span", {
433
+ "aria-hidden": "true",
434
+ className: cn("flex size-10 shrink-0 items-center justify-center rounded-full", LEVEL_RING[overall]),
435
+ children: /* @__PURE__ */ jsxDEV("span", {
436
+ className: cn("size-3 rounded-full", LEVEL_DOT[overall])
437
+ }, void 0, false)
438
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
439
+ className: "grid leading-tight",
440
+ children: [/* @__PURE__ */ jsxDEV("span", {
441
+ className: "text-sm font-semibold text-foreground",
442
+ "data-testid": "hl-status",
443
+ children: statusLabel
444
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
445
+ className: "text-[13px] text-muted-foreground",
446
+ children: statusDescription
447
+ }, void 0, false)]
448
+ }, void 0, true)]
449
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
450
+ className: "flex flex-wrap items-center gap-4",
451
+ children: [/* @__PURE__ */ jsxDEV("div", {
452
+ className: "text-end",
453
+ children: [/* @__PURE__ */ jsxDEV("div", {
454
+ className: "font-mono text-[10px] tracking-wide text-muted-foreground uppercase",
455
+ children: t("Requests")
456
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
457
+ className: "flex items-center justify-end gap-2",
458
+ children: [trend.requests.length >= 2 && /* @__PURE__ */ jsxDEV(Sparkline, {
459
+ ariaLabel: t("Requests over time"),
460
+ className: "h-6 w-20 text-foreground",
461
+ series: trend.requests,
462
+ testId: "hl-spark-requests"
463
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
464
+ className: "text-lg font-semibold tabular-nums text-foreground",
465
+ "data-testid": "hl-requests",
466
+ children: (totals?.requests ?? 0).toString()
467
+ }, void 0, false)]
468
+ }, void 0, true)]
469
+ }, void 0, true), /* @__PURE__ */ jsxDEV("span", {
470
+ "aria-hidden": "true",
471
+ className: "h-9 w-px bg-border"
472
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
473
+ className: "text-end",
474
+ children: [/* @__PURE__ */ jsxDEV("div", {
475
+ className: "font-mono text-[10px] tracking-wide text-muted-foreground uppercase",
476
+ children: t("Error rate")
477
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
478
+ className: cn("text-lg font-semibold tabular-nums", LEVEL_TEXT[errorLevel]),
479
+ "data-testid": "hl-error-rate",
480
+ children: totals === null ? "—" : ratePercent(totals.errors, totals.requests)
481
+ }, void 0, false)]
482
+ }, void 0, true), /* @__PURE__ */ jsxDEV("span", {
483
+ "aria-hidden": "true",
484
+ className: "h-9 w-px bg-border"
485
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
486
+ className: "flex items-center gap-2",
487
+ children: [/* @__PURE__ */ jsxDEV(ConnectionBadge, {}, void 0, false), /* @__PURE__ */ jsxDEV(LiveError, {
488
+ message: liveError,
489
+ prefix: "hl"
490
+ }, void 0, false)]
491
+ }, void 0, true)]
492
+ }, void 0, true)]
493
+ }, void 0, true), metricsError !== null && /* @__PURE__ */ jsxDEV("div", {
494
+ className: "border-t border-border bg-destructive/5 px-4 py-2 text-sm text-destructive",
495
+ "data-testid": "hl-metrics-error",
496
+ role: "alert",
497
+ children: metricsError
498
+ }, void 0, false)]
499
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
500
+ className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-4",
501
+ "data-testid": "hl-slo",
502
+ children: [/* @__PURE__ */ jsxDEV(SloCard, {
503
+ chart: trend.errors.length >= 2 ? /* @__PURE__ */ jsxDEV(Sparkline, {
504
+ ariaLabel: t("Errors over time"),
505
+ className: "h-7 w-20 text-destructive",
506
+ series: trend.errors,
507
+ testId: "hl-spark-errors"
508
+ }, void 0, false) : void 0,
509
+ label: t("Error rate"),
510
+ level: errorLevel,
511
+ testId: "hl-slo-errorrate",
512
+ value: totals === null ? "—" : ratePercent(totals.errors, totals.requests)
513
+ }, void 0, false), /* @__PURE__ */ jsxDEV(SloCard, {
514
+ chart: authTrend.failures.length >= 2 ? /* @__PURE__ */ jsxDEV(Sparkline, {
515
+ ariaLabel: t("Auth failures over time"),
516
+ className: "h-7 w-20 text-destructive",
517
+ series: authTrend.failures,
518
+ testId: "hl-spark-auth"
519
+ }, void 0, false) : void 0,
520
+ label: t("Auth failures"),
521
+ level: authLevel,
522
+ testId: "hl-slo-auth",
523
+ value: auth === null ? "—" : ratePercent(auth.failures, auth.attempts)
524
+ }, void 0, false), /* @__PURE__ */ jsxDEV(SloCard, {
525
+ label: t("Scheduler backlog"),
526
+ level: backlogLevel,
527
+ testId: "hl-slo-backlog",
528
+ value: scheduler === null ? "—" : scheduler.backlog.toString()
529
+ }, void 0, false), /* @__PURE__ */ jsxDEV(SloCard, {
530
+ label: t("Migrations"),
531
+ level: migration.level,
532
+ testId: "hl-slo-migrations",
533
+ value: migrationTileValue(migration, t)
534
+ }, void 0, false)]
535
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
536
+ className: "grid gap-3 lg:grid-cols-2",
537
+ children: [/* @__PURE__ */ jsxDEV(Card, {
538
+ className: "gap-0 py-0",
539
+ "data-testid": "hl-functions",
540
+ children: [/* @__PURE__ */ jsxDEV("header", {
541
+ className: "border-b border-border px-4 py-3",
542
+ children: /* @__PURE__ */ jsxDEV("span", {
543
+ className: "font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
544
+ children: t("Functions by error rate")
545
+ }, void 0, false)
546
+ }, void 0, false), worstFunctions.length === 0 ? /* @__PURE__ */ jsxDEV("p", {
547
+ className: "px-4 py-8 text-center text-sm text-muted-foreground",
548
+ "data-testid": "hl-functions-empty",
549
+ children: t("No function activity yet.")
550
+ }, void 0, false) : /* @__PURE__ */ jsxDEV("ul", {
551
+ className: "divide-y divide-border",
552
+ children: worstFunctions.map((stat_0) => /* @__PURE__ */ jsxDEV("li", {
553
+ className: "flex flex-wrap items-center justify-between gap-2 px-4 py-2 text-xs",
554
+ "data-testid": "hl-fn-row",
555
+ children: [/* @__PURE__ */ jsxDEV("span", {
556
+ className: "truncate font-mono text-foreground",
557
+ children: stat_0.path
558
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
559
+ className: "flex shrink-0 items-center gap-2",
560
+ children: [/* @__PURE__ */ jsxDEV("span", {
561
+ className: "tabular-nums text-muted-foreground",
562
+ children: t("{count} calls", {
563
+ count: stat_0.calls.toString()
564
+ })
565
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Badge, {
566
+ variant: LEVEL_VARIANT[rateLevel(stat_0.errors / stat_0.calls, REQUEST_ERROR_WARN, REQUEST_ERROR_CRIT)],
567
+ children: ratePercent(stat_0.errors, stat_0.calls)
568
+ }, void 0, false)]
569
+ }, void 0, true)]
570
+ }, stat_0.path, true))
571
+ }, void 0, false)]
572
+ }, void 0, true), /* @__PURE__ */ jsxDEV(Card, {
573
+ className: "gap-0 py-0",
574
+ children: [/* @__PURE__ */ jsxDEV("header", {
575
+ className: "flex items-center justify-between gap-2 border-b border-border px-4 py-3",
576
+ children: [/* @__PURE__ */ jsxDEV("span", {
577
+ className: "font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
578
+ children: t("Recent errors")
579
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Badge, {
580
+ "data-testid": "hl-error-count",
581
+ variant: recentErrors.length > 0 ? "destructive" : "outline",
582
+ children: recentErrors.length
583
+ }, void 0, false)]
584
+ }, void 0, true), logsError !== null && /* @__PURE__ */ jsxDEV("p", {
585
+ className: "px-4 py-8 text-center text-sm text-destructive",
586
+ "data-testid": "hl-logs-error",
587
+ role: "alert",
588
+ children: logsError
589
+ }, void 0, false), logsError === null && topErrors.length === 0 && /* @__PURE__ */ jsxDEV("p", {
590
+ className: "px-4 py-8 text-center text-sm text-muted-foreground",
591
+ "data-testid": "hl-errors-empty",
592
+ children: t("No recent errors.")
593
+ }, void 0, false), topErrors.length > 0 && /* @__PURE__ */ jsxDEV("ul", {
594
+ className: "divide-y divide-border",
595
+ children: topErrors.map((entry_0, index) => /* @__PURE__ */ jsxDEV("li", {
596
+ className: "flex flex-col gap-0.5 px-4 py-2 text-xs",
597
+ "data-testid": "hl-error-row",
598
+ children: [/* @__PURE__ */ jsxDEV("span", {
599
+ className: "flex items-center gap-2",
600
+ children: [/* @__PURE__ */ jsxDEV("time", {
601
+ className: "shrink-0 text-muted-foreground",
602
+ children: formatTimestamp(entry_0.timestamp)
603
+ }, void 0, false), entry_0.functionPath !== void 0 && /* @__PURE__ */ jsxDEV("span", {
604
+ className: "truncate font-mono text-foreground",
605
+ children: entry_0.functionPath
606
+ }, void 0, false)]
607
+ }, void 0, true), /* @__PURE__ */ jsxDEV("span", {
608
+ className: "text-destructive",
609
+ children: entry_0.message
610
+ }, void 0, false)]
611
+ }, `${entry_0.timestamp.toString()}-${index.toString()}`, true))
612
+ }, void 0, false)]
613
+ }, void 0, true)]
614
+ }, void 0, true), /* @__PURE__ */ jsxDEV(Card, {
615
+ className: "gap-0 py-0",
616
+ children: [/* @__PURE__ */ jsxDEV("header", {
617
+ className: "flex items-center justify-between gap-2 border-b border-border px-4 py-3",
618
+ children: [/* @__PURE__ */ jsxDEV("span", {
619
+ className: "font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
620
+ children: t("Shards seen")
621
+ }, void 0, false), /* @__PURE__ */ jsxDEV(Badge, {
622
+ "data-testid": "hl-shard-count",
623
+ variant: "outline",
624
+ children: recentShards.length
625
+ }, void 0, false)]
626
+ }, void 0, true), recentShards.length > 0 && /* @__PURE__ */ jsxDEV("ul", {
627
+ className: "flex flex-wrap gap-1.5 p-4",
628
+ children: recentShards.map((shard) => /* @__PURE__ */ jsxDEV("li", {
629
+ "data-testid": "hl-shard",
630
+ children: /* @__PURE__ */ jsxDEV(Badge, {
631
+ variant: "secondary",
632
+ children: shard
633
+ }, void 0, false)
634
+ }, shard, false))
635
+ }, void 0, false)]
636
+ }, void 0, true)]
637
+ }, void 0, true);
638
+ };
639
+
640
+ export { HealthPanel };