sermon-js 0.0.0-stage → 0.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -2
  3. package/dist/api/index.cjs +174 -0
  4. package/dist/api/index.cjs.map +1 -0
  5. package/dist/api/index.d.cts +77 -0
  6. package/dist/api/index.d.ts +77 -0
  7. package/dist/api/index.js +171 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/chunk-2M4UBCDM.js +178 -0
  10. package/dist/chunk-2M4UBCDM.js.map +1 -0
  11. package/dist/chunk-AH372HLN.js +98 -0
  12. package/dist/chunk-AH372HLN.js.map +1 -0
  13. package/dist/chunk-APC2HVSI.js +154 -0
  14. package/dist/chunk-APC2HVSI.js.map +1 -0
  15. package/dist/chunk-D7BBJTYU.cjs +335 -0
  16. package/dist/chunk-D7BBJTYU.cjs.map +1 -0
  17. package/dist/chunk-ELV4ZU6G.cjs +51 -0
  18. package/dist/chunk-ELV4ZU6G.cjs.map +1 -0
  19. package/dist/chunk-EMP7XVKC.js +334 -0
  20. package/dist/chunk-EMP7XVKC.js.map +1 -0
  21. package/dist/chunk-I3346MUL.js +49 -0
  22. package/dist/chunk-I3346MUL.js.map +1 -0
  23. package/dist/chunk-NP3YYVRZ.cjs +163 -0
  24. package/dist/chunk-NP3YYVRZ.cjs.map +1 -0
  25. package/dist/chunk-PARE6EUZ.cjs +184 -0
  26. package/dist/chunk-PARE6EUZ.cjs.map +1 -0
  27. package/dist/chunk-XHXEKB3Q.cjs +241 -0
  28. package/dist/chunk-XHXEKB3Q.cjs.map +1 -0
  29. package/dist/chunk-XPXIDJUU.js +229 -0
  30. package/dist/chunk-XPXIDJUU.js.map +1 -0
  31. package/dist/chunk-ZI442ZA5.cjs +100 -0
  32. package/dist/chunk-ZI442ZA5.cjs.map +1 -0
  33. package/dist/config-BEbP-uLf.d.cts +125 -0
  34. package/dist/config-w1tm1bdu.d.ts +125 -0
  35. package/dist/generic-BJmlgtUM.d.ts +31 -0
  36. package/dist/generic-lmrGxMDC.d.cts +31 -0
  37. package/dist/index.cjs +407 -0
  38. package/dist/index.cjs.map +1 -0
  39. package/dist/index.d.cts +93 -0
  40. package/dist/index.d.ts +93 -0
  41. package/dist/index.js +330 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/presets/index.cjs +1012 -0
  44. package/dist/presets/index.cjs.map +1 -0
  45. package/dist/presets/index.d.cts +516 -0
  46. package/dist/presets/index.d.ts +516 -0
  47. package/dist/presets/index.js +971 -0
  48. package/dist/presets/index.js.map +1 -0
  49. package/dist/react/heroui/index.cjs +822 -0
  50. package/dist/react/heroui/index.cjs.map +1 -0
  51. package/dist/react/heroui/index.d.cts +122 -0
  52. package/dist/react/heroui/index.d.ts +122 -0
  53. package/dist/react/heroui/index.js +821 -0
  54. package/dist/react/heroui/index.js.map +1 -0
  55. package/dist/react/heroui/styles.css +11 -0
  56. package/dist/react/index.cjs +71 -0
  57. package/dist/react/index.cjs.map +1 -0
  58. package/dist/react/index.d.cts +53 -0
  59. package/dist/react/index.d.ts +53 -0
  60. package/dist/react/index.js +70 -0
  61. package/dist/react/index.js.map +1 -0
  62. package/dist/status-DsWzgb9Q.d.cts +146 -0
  63. package/dist/status-zd04ZQ98.d.ts +146 -0
  64. package/dist/store-DybhDphS.d.cts +77 -0
  65. package/dist/store-_GuzSP0z.d.ts +77 -0
  66. package/dist/stores/mongodb.cjs +99 -0
  67. package/dist/stores/mongodb.cjs.map +1 -0
  68. package/dist/stores/mongodb.d.cts +40 -0
  69. package/dist/stores/mongodb.d.ts +40 -0
  70. package/dist/stores/mongodb.js +97 -0
  71. package/dist/stores/mongodb.js.map +1 -0
  72. package/dist/stores/redis.cjs +23 -0
  73. package/dist/stores/redis.cjs.map +1 -0
  74. package/dist/stores/redis.d.cts +31 -0
  75. package/dist/stores/redis.d.ts +31 -0
  76. package/dist/stores/redis.js +21 -0
  77. package/dist/stores/redis.js.map +1 -0
  78. package/dist/stores/sql.cjs +164 -0
  79. package/dist/stores/sql.cjs.map +1 -0
  80. package/dist/stores/sql.d.cts +39 -0
  81. package/dist/stores/sql.d.ts +39 -0
  82. package/dist/stores/sql.js +162 -0
  83. package/dist/stores/sql.js.map +1 -0
  84. package/dist/stores/upstash.cjs +27 -0
  85. package/dist/stores/upstash.cjs.map +1 -0
  86. package/dist/stores/upstash.d.cts +19 -0
  87. package/dist/stores/upstash.d.ts +19 -0
  88. package/dist/stores/upstash.js +25 -0
  89. package/dist/stores/upstash.js.map +1 -0
  90. package/dist/testing/index.cjs +209 -0
  91. package/dist/testing/index.cjs.map +1 -0
  92. package/dist/testing/index.d.cts +66 -0
  93. package/dist/testing/index.d.ts +66 -0
  94. package/dist/testing/index.js +205 -0
  95. package/dist/testing/index.js.map +1 -0
  96. package/dist/tree-BXbXfjF1.d.cts +236 -0
  97. package/dist/tree-BXbXfjF1.d.ts +236 -0
  98. package/dist/types-CctvUAs2.d.cts +192 -0
  99. package/dist/types-CctvUAs2.d.ts +192 -0
  100. package/package.json +233 -3
@@ -0,0 +1,821 @@
1
+ "use client";
2
+ import {
3
+ FOCUS_EVENT,
4
+ LABEL_TEXT,
5
+ LABEL_TONE,
6
+ RateLimitError,
7
+ STATUS_TEXT,
8
+ STATUS_TONE,
9
+ StatusProvider,
10
+ buildLayout,
11
+ currentAnchor,
12
+ flattenServices,
13
+ focusAnchor,
14
+ formatDateTime,
15
+ formatDuration,
16
+ formatMs,
17
+ formatPct,
18
+ groupAnchor,
19
+ humanize,
20
+ isStale,
21
+ mergeBars,
22
+ serviceAnchor,
23
+ servicesOf,
24
+ statusOf,
25
+ timeAgo,
26
+ useHistory,
27
+ useRunChecks,
28
+ useStatus,
29
+ useThemePreference
30
+ } from "../../chunk-EMP7XVKC.js";
31
+
32
+ // src/react/heroui/StatusPage.tsx
33
+ import { Card as Card3 } from "@heroui/react";
34
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
35
+ import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2 } from "react";
36
+
37
+ // src/react/heroui/controls.tsx
38
+ import { Button, ToggleButton, ToggleButtonGroup } from "@heroui/react";
39
+
40
+ // src/react/heroui/icons.tsx
41
+ import { jsx, jsxs } from "react/jsx-runtime";
42
+ function Icon({ size = 16, children, ...props }) {
43
+ return /* @__PURE__ */ jsx(
44
+ "svg",
45
+ {
46
+ width: size,
47
+ height: size,
48
+ viewBox: "0 0 24 24",
49
+ fill: "none",
50
+ stroke: "currentColor",
51
+ strokeWidth: 2,
52
+ strokeLinecap: "round",
53
+ strokeLinejoin: "round",
54
+ "aria-hidden": true,
55
+ ...props,
56
+ children
57
+ }
58
+ );
59
+ }
60
+ var RefreshIcon = (props) => /* @__PURE__ */ jsxs(Icon, { ...props, children: [
61
+ /* @__PURE__ */ jsx("path", { d: "M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4" }),
62
+ /* @__PURE__ */ jsx("path", { d: "M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" })
63
+ ] });
64
+ var PlayIcon = (props) => /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M7 4v16l13-8z" }) });
65
+ var DesktopIcon = (props) => /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M3 5a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zM7 20h10M9 16v4M15 16v4" }) });
66
+ var SunIcon = (props) => /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M8 12a4 4 0 1 0 8 0a4 4 0 1 0-8 0M3 12h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7l-.7.7m0 11.4l.7.7m-12.1-.7l-.7.7" }) });
67
+ var MoonIcon = (props) => /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M12 3h.4A7.5 7.5 0 0 0 20 14.4A9 9 0 1 1 12 3" }) });
68
+ var AlertIcon = (props) => /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M12 9v4m0 4h.01M10.4 3.6L2.3 17.2a1.9 1.9 0 0 0 1.6 2.9h16.2a1.9 1.9 0 0 0 1.6-2.9L13.6 3.6a1.9 1.9 0 0 0-3.2 0" }) });
69
+
70
+ // src/react/heroui/controls.tsx
71
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
72
+ function RunAllButton({ label = "Run all checks" }) {
73
+ const run = useRunChecks();
74
+ const rateLimited = run.error instanceof RateLimitError ? run.error : void 0;
75
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
76
+ rateLimited && /* @__PURE__ */ jsxs2("span", { className: "text-xs text-muted", children: [
77
+ "Try again in ",
78
+ rateLimited.retryAfter,
79
+ "s"
80
+ ] }),
81
+ run.error && !rateLimited && /* @__PURE__ */ jsx2("span", { className: "text-xs text-danger", children: run.error.message }),
82
+ /* @__PURE__ */ jsxs2(Button, { size: "sm", variant: "tertiary", isDisabled: run.isPending, onPress: () => run.mutate(), children: [
83
+ /* @__PURE__ */ jsx2(RefreshIcon, { size: 16, className: run.isPending ? "motion-safe:animate-spin" : void 0 }),
84
+ run.isPending ? "Running checks\u2026" : label
85
+ ] })
86
+ ] });
87
+ }
88
+ var THEMES = [
89
+ { id: "system", label: "System theme", Icon: DesktopIcon },
90
+ { id: "light", label: "Light theme", Icon: SunIcon },
91
+ { id: "dark", label: "Dark theme", Icon: MoonIcon }
92
+ ];
93
+ function ThemeSwitcher() {
94
+ const [preference, setPreference] = useThemePreference();
95
+ return /* @__PURE__ */ jsx2(
96
+ ToggleButtonGroup,
97
+ {
98
+ "aria-label": "Color theme",
99
+ size: "sm",
100
+ disallowEmptySelection: true,
101
+ selectedKeys: [preference],
102
+ onSelectionChange: (keys) => {
103
+ const [key] = [...keys];
104
+ if (key) setPreference(key);
105
+ },
106
+ className: "opacity-60 transition-opacity hover:opacity-100 focus-within:opacity-100",
107
+ children: THEMES.map(({ id, label, Icon: Icon2 }, index) => /* @__PURE__ */ jsxs2(ToggleButton, { id, isIconOnly: true, "aria-label": label, children: [
108
+ index > 0 && /* @__PURE__ */ jsx2(ToggleButtonGroup.Separator, {}),
109
+ /* @__PURE__ */ jsx2(Icon2, { size: 14 })
110
+ ] }, id))
111
+ }
112
+ );
113
+ }
114
+ function StaleNotice({ snapshot }) {
115
+ if (!isStale(snapshot, snapshot.generatedAt) || snapshot.lastRunAt === null) return null;
116
+ return /* @__PURE__ */ jsxs2("div", { role: "status", className: "flex items-start gap-2 rounded-lg bg-warning/10 px-3 py-2 text-sm text-warning", children: [
117
+ /* @__PURE__ */ jsx2(AlertIcon, { size: 16, className: "mt-0.5 shrink-0" }),
118
+ /* @__PURE__ */ jsxs2("span", { children: [
119
+ "These results are out of date: the last full check ran ",
120
+ timeAgo(snapshot.lastRunAt),
121
+ ", and checks should run every ",
122
+ formatDuration(snapshot.intervalMs),
123
+ "."
124
+ ] })
125
+ ] });
126
+ }
127
+
128
+ // src/react/heroui/details.tsx
129
+ import { Label, Meter } from "@heroui/react";
130
+
131
+ // src/react/heroui/tones.ts
132
+ var TONE_BG = {
133
+ success: "bg-success",
134
+ warning: "bg-warning",
135
+ danger: "bg-danger",
136
+ accent: "bg-accent",
137
+ default: "bg-default"
138
+ };
139
+ var TONE_TEXT = {
140
+ success: "text-success",
141
+ warning: "text-warning",
142
+ danger: "text-danger",
143
+ accent: "text-accent",
144
+ default: "text-muted"
145
+ };
146
+
147
+ // src/react/heroui/details.tsx
148
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
149
+ var WIDTH = 120;
150
+ var HEIGHT = 28;
151
+ function Sparkline({ values, threshold }) {
152
+ const points = values.slice(-96);
153
+ const measured = points.filter((value) => value !== null);
154
+ if (measured.length < 2) {
155
+ return /* @__PURE__ */ jsx3("div", { style: { width: WIDTH, height: HEIGHT }, className: "text-[10px] text-muted", children: "no latency data" });
156
+ }
157
+ const max = Math.max(threshold, ...measured);
158
+ const step = WIDTH / Math.max(1, points.length - 1);
159
+ const y = (value) => HEIGHT - 2 - value / max * (HEIGHT - 4);
160
+ let path = "";
161
+ points.forEach((value, index) => {
162
+ if (value === null) return;
163
+ const previous = points[index - 1];
164
+ path += `${previous === null || previous === void 0 ? "M" : "L"}${(index * step).toFixed(1)},${y(value).toFixed(1)} `;
165
+ });
166
+ return /* @__PURE__ */ jsxs3("svg", { width: WIDTH, height: HEIGHT, viewBox: `0 0 ${WIDTH} ${HEIGHT}`, "aria-hidden": true, className: "shrink-0", children: [
167
+ /* @__PURE__ */ jsx3(
168
+ "line",
169
+ {
170
+ x1: 0,
171
+ x2: WIDTH,
172
+ y1: y(threshold),
173
+ y2: y(threshold),
174
+ className: "stroke-warning",
175
+ strokeDasharray: "2 3",
176
+ strokeWidth: 1,
177
+ opacity: 0.6
178
+ }
179
+ ),
180
+ /* @__PURE__ */ jsx3("path", { d: path, fill: "none", stroke: "currentColor", strokeWidth: 1.5, className: "text-accent" })
181
+ ] });
182
+ }
183
+ var number = new Intl.NumberFormat("en");
184
+ function UsageBar({ usage }) {
185
+ const pct = usage.limit > 0 ? usage.used / usage.limit * 100 : 0;
186
+ const unit = usage.unit ? ` ${usage.unit}` : "";
187
+ return /* @__PURE__ */ jsxs3("div", { className: "flex w-full max-w-md flex-col gap-1", children: [
188
+ /* @__PURE__ */ jsxs3(
189
+ Meter,
190
+ {
191
+ value: Math.min(usage.used, usage.limit),
192
+ maxValue: usage.limit,
193
+ color: pct >= 90 ? "danger" : pct >= 75 ? "warning" : "success",
194
+ size: "sm",
195
+ valueLabel: /* @__PURE__ */ jsxs3("span", { className: "tabular-nums", children: [
196
+ number.format(usage.used),
197
+ " / ",
198
+ number.format(usage.limit),
199
+ unit,
200
+ " \xB7 ",
201
+ pct.toFixed(pct < 10 ? 1 : 0),
202
+ "%"
203
+ ] }),
204
+ children: [
205
+ /* @__PURE__ */ jsx3(Label, { className: "text-xs", children: usage.label }),
206
+ /* @__PURE__ */ jsx3(Meter.Output, { className: "text-xs" }),
207
+ /* @__PURE__ */ jsx3(Meter.Track, { children: /* @__PURE__ */ jsx3(Meter.Fill, {}) })
208
+ ]
209
+ }
210
+ ),
211
+ usage.resetsAt && /* @__PURE__ */ jsxs3("span", { className: "text-[11px] text-muted", children: [
212
+ number.format(usage.limit - usage.used),
213
+ unit,
214
+ " left \xB7 resets ",
215
+ timeAgo(usage.resetsAt)
216
+ ] })
217
+ ] });
218
+ }
219
+ var FLY_REGIONS = {
220
+ ams: "Amsterdam",
221
+ arn: "Stockholm",
222
+ atl: "Atlanta",
223
+ bog: "Bogot\xE1",
224
+ bom: "Mumbai",
225
+ bos: "Boston",
226
+ cdg: "Paris",
227
+ den: "Denver",
228
+ dfw: "Dallas",
229
+ ewr: "Secaucus",
230
+ eze: "Buenos Aires",
231
+ fra: "Frankfurt",
232
+ gdl: "Guadalajara",
233
+ gig: "Rio de Janeiro",
234
+ gru: "S\xE3o Paulo",
235
+ hkg: "Hong Kong",
236
+ iad: "Ashburn",
237
+ jnb: "Johannesburg",
238
+ lax: "Los Angeles",
239
+ lhr: "London",
240
+ mad: "Madrid",
241
+ mia: "Miami",
242
+ nrt: "Tokyo",
243
+ ord: "Chicago",
244
+ otp: "Bucharest",
245
+ phx: "Phoenix",
246
+ qro: "Quer\xE9taro",
247
+ scl: "Santiago",
248
+ sea: "Seattle",
249
+ sin: "Singapore",
250
+ sjc: "San Jose",
251
+ syd: "Sydney",
252
+ waw: "Warsaw",
253
+ yul: "Montreal",
254
+ yyz: "Toronto"
255
+ };
256
+ function InstanceTable({
257
+ instances,
258
+ stats,
259
+ regionNames = FLY_REGIONS
260
+ }) {
261
+ return /* @__PURE__ */ jsx3("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs3("table", { className: "w-full min-w-[34rem] text-xs", children: [
262
+ /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs3("tr", { className: "text-left text-[10px] uppercase tracking-wide text-muted", children: [
263
+ /* @__PURE__ */ jsx3("th", { className: "py-1 pr-3 font-normal", children: "Region" }),
264
+ /* @__PURE__ */ jsx3("th", { className: "py-1 pr-3 font-normal", children: "Machine" }),
265
+ /* @__PURE__ */ jsx3("th", { className: "py-1 pr-3 font-normal", children: "Status" }),
266
+ /* @__PURE__ */ jsx3("th", { className: "py-1 pr-3 text-right font-normal", children: "Response" }),
267
+ /* @__PURE__ */ jsx3("th", { className: "py-1 pr-3 text-right font-normal", children: "p50 24h" }),
268
+ /* @__PURE__ */ jsx3("th", { className: "py-1 text-right font-normal", children: "Uptime 24h" })
269
+ ] }) }),
270
+ /* @__PURE__ */ jsx3("tbody", { className: "divide-y divide-border", children: instances.map((instance) => {
271
+ const tone = STATUS_TONE[instance.status];
272
+ const instanceStats = stats?.[instance.key];
273
+ return /* @__PURE__ */ jsxs3("tr", { className: "align-top", children: [
274
+ /* @__PURE__ */ jsx3("td", { className: "py-1.5 pr-3", children: /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-2", children: [
275
+ /* @__PURE__ */ jsx3("span", { className: `size-2 shrink-0 rounded-full ${TONE_BG[tone]}`, "aria-hidden": true }),
276
+ /* @__PURE__ */ jsx3("span", { className: "font-mono", children: instance.key }),
277
+ /* @__PURE__ */ jsx3("span", { className: "text-muted", children: (instance.region && regionNames[instance.region]) ?? "" })
278
+ ] }) }),
279
+ /* @__PURE__ */ jsx3("td", { className: "py-1.5 pr-3 font-mono text-muted", title: instance.id, children: [instance.id?.slice(0, 8), instance.state].filter(Boolean).join(" \xB7 ") }),
280
+ /* @__PURE__ */ jsxs3("td", { className: "py-1.5 pr-3", children: [
281
+ /* @__PURE__ */ jsx3("span", { className: TONE_TEXT[tone], children: STATUS_TEXT[instance.status] }),
282
+ instance.error && instance.status !== "up" && /* @__PURE__ */ jsxs3("span", { className: "block text-muted", children: [
283
+ instance.httpStatus ? `[${instance.httpStatus}] ` : "",
284
+ instance.error
285
+ ] })
286
+ ] }),
287
+ /* @__PURE__ */ jsx3("td", { className: "py-1.5 pr-3 text-right tabular-nums", children: instance.latencyMs !== void 0 ? formatMs(instance.latencyMs) : "\u2013" }),
288
+ /* @__PURE__ */ jsx3("td", { className: "py-1.5 pr-3 text-right tabular-nums", children: formatMs(instanceStats?.p50) }),
289
+ /* @__PURE__ */ jsx3("td", { className: "py-1.5 text-right tabular-nums", children: formatPct(instanceStats?.uptime24h) })
290
+ ] }, instance.key);
291
+ }) })
292
+ ] }) });
293
+ }
294
+
295
+ // src/react/heroui/HistoryBars.tsx
296
+ import { Chip, Tabs } from "@heroui/react";
297
+ import { useEffect, useMemo, useRef, useState } from "react";
298
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
299
+ var MIN_BAR_PX = 3;
300
+ function average(bars) {
301
+ const scored = bars.filter((bar) => bar.score !== null);
302
+ if (!scored.length) return null;
303
+ return Math.round(scored.reduce((sum, bar) => sum + bar.score, 0) / scored.length);
304
+ }
305
+ function HistoryBars({ names = {} }) {
306
+ const [range, setRange] = useState("24h");
307
+ const [hovered, setHovered] = useState(null);
308
+ const history2 = useHistory(range);
309
+ const chart = useRef(null);
310
+ const [width, setWidth] = useState(null);
311
+ useEffect(() => {
312
+ const element = chart.current;
313
+ if (!element) return;
314
+ const measure = () => setWidth(element.clientWidth);
315
+ measure();
316
+ if (typeof ResizeObserver === "undefined") return;
317
+ const observer = new ResizeObserver(measure);
318
+ observer.observe(element);
319
+ return () => observer.disconnect();
320
+ }, []);
321
+ const { bars, factor } = useMemo(
322
+ () => mergeBars(history2.data?.bars ?? [], width ? Math.floor(width / MIN_BAR_PX) : Infinity),
323
+ [history2.data, width]
324
+ );
325
+ const active = hovered !== null && bars[hovered] || bars.findLast((bar) => bar.runs > 0);
326
+ const avg = average(bars);
327
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-3", children: [
328
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
329
+ /* @__PURE__ */ jsxs4("div", { children: [
330
+ /* @__PURE__ */ jsx4("h3", { className: "text-sm font-medium", children: "System score history" }),
331
+ /* @__PURE__ */ jsxs4("p", { className: "text-xs text-muted", children: [
332
+ history2.data ? `Worst score per ${formatDuration(history2.data.bucketMs * factor)}` : "Loading\u2026",
333
+ avg !== null && ` \xB7 average ${avg}`
334
+ ] })
335
+ ] }),
336
+ /* @__PURE__ */ jsx4(Tabs, { selectedKey: range, onSelectionChange: (key) => setRange(key), children: /* @__PURE__ */ jsx4(Tabs.ListContainer, { children: /* @__PURE__ */ jsxs4(Tabs.List, { "aria-label": "History range", children: [
337
+ /* @__PURE__ */ jsxs4(Tabs.Tab, { id: "24h", children: [
338
+ "24h",
339
+ /* @__PURE__ */ jsx4(Tabs.Indicator, {})
340
+ ] }),
341
+ /* @__PURE__ */ jsxs4(Tabs.Tab, { id: "7d", children: [
342
+ "7d",
343
+ /* @__PURE__ */ jsx4(Tabs.Indicator, {})
344
+ ] })
345
+ ] }) }) })
346
+ ] }),
347
+ /* @__PURE__ */ jsxs4(
348
+ "div",
349
+ {
350
+ ref: chart,
351
+ className: "flex h-24 items-end gap-px",
352
+ onMouseLeave: () => setHovered(null),
353
+ role: "img",
354
+ "aria-label": `System score over the last ${range}`,
355
+ children: [
356
+ history2.isPending && Array.from({ length: 48 }, (_, index) => /* @__PURE__ */ jsx4("div", { className: "h-1/3 flex-1 animate-pulse rounded-sm bg-default" }, index)),
357
+ bars.map((bar, index) => /* @__PURE__ */ jsx4(
358
+ "div",
359
+ {
360
+ className: `relative h-full flex-1 cursor-default overflow-hidden rounded-sm bg-default transition-opacity ${hovered !== null && hovered !== index ? "opacity-50" : ""}`,
361
+ onMouseEnter: () => setHovered(index),
362
+ children: bar.score !== null && /* @__PURE__ */ jsx4(
363
+ "div",
364
+ {
365
+ className: `absolute inset-x-0 bottom-0 ${TONE_BG[LABEL_TONE[bar.label]]}`,
366
+ style: { height: `${Math.max(3, bar.score)}%` }
367
+ }
368
+ )
369
+ },
370
+ bar.start
371
+ ))
372
+ ]
373
+ }
374
+ ),
375
+ /* @__PURE__ */ jsxs4("div", { className: "flex justify-between text-[11px] text-muted", children: [
376
+ /* @__PURE__ */ jsx4("span", { children: range === "24h" ? "24 hours ago" : "7 days ago" }),
377
+ /* @__PURE__ */ jsx4("span", { children: "Now" })
378
+ ] }),
379
+ /* @__PURE__ */ jsx4("div", { className: "min-h-10 rounded-lg bg-surface-secondary px-3 py-2 text-xs", children: active ? /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
380
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-x-3", children: [
381
+ /* @__PURE__ */ jsx4("span", { className: "font-medium", children: formatDateTime(active.start) }),
382
+ /* @__PURE__ */ jsx4("span", { className: TONE_TEXT[LABEL_TONE[active.label]], children: active.score === null ? "No data" : `Score ${active.score} \xB7 ${LABEL_TEXT[active.label]}` }),
383
+ active.runs > 0 && /* @__PURE__ */ jsxs4("span", { className: "text-muted", children: [
384
+ active.runs,
385
+ " run(s)"
386
+ ] })
387
+ ] }),
388
+ Object.keys(active.failing).length > 0 && /* @__PURE__ */ jsx4("div", { className: "mt-1 flex flex-wrap gap-1.5", children: Object.entries(active.failing).map(([key, status]) => /* @__PURE__ */ jsx4(
389
+ Chip,
390
+ {
391
+ size: "sm",
392
+ variant: "soft",
393
+ color: status === "down" ? "danger" : "warning",
394
+ "aria-label": `${names[key] ?? key}: ${status}`,
395
+ children: names[key] ?? key
396
+ },
397
+ key
398
+ )) })
399
+ ] }) : /* @__PURE__ */ jsx4("span", { className: "text-muted", children: "Hover a bar to see details" }) })
400
+ ] });
401
+ }
402
+
403
+ // src/react/heroui/ScoreHeader.tsx
404
+ import { Chip as Chip2 } from "@heroui/react";
405
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
406
+ var SIZE = 88;
407
+ var STROKE = 8;
408
+ var RADIUS = (SIZE - STROKE) / 2;
409
+ var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
410
+ function ScoreRing({ score, tone }) {
411
+ const progress = score === null ? 0 : score / 100;
412
+ return /* @__PURE__ */ jsxs5("div", { className: "relative shrink-0", style: { width: SIZE, height: SIZE }, children: [
413
+ /* @__PURE__ */ jsxs5("svg", { width: SIZE, height: SIZE, viewBox: `0 0 ${SIZE} ${SIZE}`, className: "-rotate-90", children: [
414
+ /* @__PURE__ */ jsx5("circle", { cx: SIZE / 2, cy: SIZE / 2, r: RADIUS, fill: "none", strokeWidth: STROKE, className: "stroke-default" }),
415
+ /* @__PURE__ */ jsx5(
416
+ "circle",
417
+ {
418
+ cx: SIZE / 2,
419
+ cy: SIZE / 2,
420
+ r: RADIUS,
421
+ fill: "none",
422
+ strokeWidth: STROKE,
423
+ strokeLinecap: "round",
424
+ strokeDasharray: CIRCUMFERENCE,
425
+ strokeDashoffset: CIRCUMFERENCE * (1 - progress),
426
+ className: `${TONE_TEXT[tone]} transition-[stroke-dashoffset] duration-700`,
427
+ stroke: "currentColor"
428
+ }
429
+ )
430
+ ] }),
431
+ /* @__PURE__ */ jsxs5("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: [
432
+ /* @__PURE__ */ jsx5("span", { className: "text-2xl font-semibold tabular-nums", children: score ?? "\u2013" }),
433
+ /* @__PURE__ */ jsx5("span", { className: "text-[10px] uppercase tracking-wide text-muted", children: "score" })
434
+ ] })
435
+ ] });
436
+ }
437
+ var COUNT_ORDER = ["up", "degraded", "down", "sleeping", "unconfigured"];
438
+ function ScoreHeader({ snapshot }) {
439
+ const tone = LABEL_TONE[snapshot.label];
440
+ return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-5 sm:flex-row sm:items-center", children: [
441
+ /* @__PURE__ */ jsx5(ScoreRing, { score: snapshot.score, tone }),
442
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
443
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
444
+ /* @__PURE__ */ jsx5("h2", { className: "text-xl font-semibold", children: LABEL_TEXT[snapshot.label] }),
445
+ /* @__PURE__ */ jsx5(Chip2, { color: tone, variant: "soft", size: "sm", children: snapshot.label })
446
+ ] }),
447
+ /* @__PURE__ */ jsx5("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted", children: COUNT_ORDER.filter((status) => snapshot.counts[status] > 0).map((status) => /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-1.5", children: [
448
+ /* @__PURE__ */ jsx5("span", { className: `size-2 rounded-full ${TONE_BG[STATUS_TONE[status]]}` }),
449
+ snapshot.counts[status],
450
+ " ",
451
+ STATUS_TEXT[status].toLowerCase()
452
+ ] }, status)) }),
453
+ /* @__PURE__ */ jsx5("p", { className: "text-xs text-muted", children: snapshot.lastCheckedAt ? `Last checked ${timeAgo(snapshot.lastCheckedAt)} \xB7 checks run every ${formatDuration(snapshot.intervalMs)}` : "No checks have run yet" })
454
+ ] })
455
+ ] });
456
+ }
457
+
458
+ // src/react/heroui/ServiceNav.tsx
459
+ import { Card } from "@heroui/react";
460
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
461
+ function NavLink({
462
+ anchor,
463
+ className,
464
+ children,
465
+ label
466
+ }) {
467
+ return /* @__PURE__ */ jsx6(
468
+ "a",
469
+ {
470
+ href: `#${encodeURIComponent(anchor)}`,
471
+ "aria-label": label,
472
+ className,
473
+ onClick: (event) => {
474
+ if (event.metaKey || event.ctrlKey || event.shiftKey) return;
475
+ event.preventDefault();
476
+ focusAnchor(anchor);
477
+ },
478
+ children
479
+ }
480
+ );
481
+ }
482
+ function SectionLinks({ section }) {
483
+ return /* @__PURE__ */ jsxs6("li", { children: [
484
+ /* @__PURE__ */ jsx6(
485
+ NavLink,
486
+ {
487
+ anchor: groupAnchor(section.key),
488
+ className: "block rounded px-1.5 pt-1 text-[11px] leading-4 font-medium text-muted hover:text-foreground",
489
+ children: section.title
490
+ }
491
+ ),
492
+ /* @__PURE__ */ jsx6("ul", { children: servicesOf(section).map((service) => {
493
+ const status = statusOf(service);
494
+ return /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs6(
495
+ NavLink,
496
+ {
497
+ anchor: serviceAnchor(service.key),
498
+ label: `${service.name}: ${STATUS_TEXT[status]}`,
499
+ className: "flex items-center gap-1.5 rounded px-1.5 text-xs leading-[18px] hover:bg-default",
500
+ children: [
501
+ /* @__PURE__ */ jsx6("span", { className: `size-1.5 shrink-0 rounded-full ${TONE_BG[STATUS_TONE[status]]}`, "aria-hidden": true }),
502
+ /* @__PURE__ */ jsx6("span", { className: "truncate", children: service.name })
503
+ ]
504
+ }
505
+ ) }, service.key);
506
+ }) })
507
+ ] });
508
+ }
509
+ function ServiceNav({ blocks, title = "Services" }) {
510
+ return /* @__PURE__ */ jsxs6(Card, { className: "max-h-[calc(100vh-2rem)] gap-1 overflow-y-auto p-3", children: [
511
+ /* @__PURE__ */ jsx6(Card.Header, { children: /* @__PURE__ */ jsx6(Card.Title, { className: "px-1.5 text-xs font-semibold", children: title }) }),
512
+ /* @__PURE__ */ jsx6(Card.Content, { children: /* @__PURE__ */ jsx6("nav", { "aria-label": "Tracked services", children: /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-0.5", children: blocks.map(
513
+ (block) => block.type === "section" ? /* @__PURE__ */ jsx6(SectionLinks, { section: block.section }, block.section.key) : /* @__PURE__ */ jsxs6("li", { children: [
514
+ /* @__PURE__ */ jsx6("p", { className: "mt-2 px-1.5 text-[10px] leading-4 font-semibold uppercase tracking-wide text-muted", children: block.title }),
515
+ /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-0.5", children: block.sections.map((section) => /* @__PURE__ */ jsx6(SectionLinks, { section }, section.key)) })
516
+ ] }, block.key)
517
+ ) }) }) })
518
+ ] });
519
+ }
520
+
521
+ // src/react/heroui/ServiceRow.tsx
522
+ import { Button as Button2, Chip as Chip3 } from "@heroui/react";
523
+ import { createContext, useContext } from "react";
524
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
525
+ var KitOptions = createContext({
526
+ regionNames: FLY_REGIONS,
527
+ allowRuns: true
528
+ });
529
+ function Stat({ label, value }) {
530
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col", children: [
531
+ /* @__PURE__ */ jsx7("span", { className: "text-[10px] uppercase tracking-wide text-muted", children: label }),
532
+ /* @__PURE__ */ jsx7("span", { className: "text-sm tabular-nums", children: value })
533
+ ] });
534
+ }
535
+ var DAY_MS = 24 * 60 * 60 * 1e3;
536
+ function ServiceRow({ service }) {
537
+ const run = useRunChecks();
538
+ const { regionNames, allowRuns } = useContext(KitOptions);
539
+ const status = statusOf(service);
540
+ const tone = STATUS_TONE[status];
541
+ const { result, stats } = service;
542
+ const failing = status === "down" || status === "degraded";
543
+ const rateLimited = run.error instanceof RateLimitError ? run.error : void 0;
544
+ const lastFailure = !failing && result && stats?.lastFailure && result.checkedAt - stats.lastFailure.at < DAY_MS ? stats.lastFailure : void 0;
545
+ return /* @__PURE__ */ jsxs7(
546
+ "div",
547
+ {
548
+ id: serviceAnchor(service.key),
549
+ className: `flex scroll-mt-6 flex-col gap-3 py-4 ${service.configured ? "" : "opacity-60"}`,
550
+ children: [
551
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-start gap-3", children: [
552
+ /* @__PURE__ */ jsx7("span", { className: `mt-1.5 size-2.5 shrink-0 rounded-full ${TONE_BG[tone]}`, "aria-hidden": true }),
553
+ /* @__PURE__ */ jsxs7("div", { className: "min-w-0 flex-1", children: [
554
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-2", children: [
555
+ /* @__PURE__ */ jsx7("span", { className: "font-medium", children: service.name }),
556
+ service.critical && /* @__PURE__ */ jsx7(Chip3, { size: "sm", variant: "tertiary", children: "critical" }),
557
+ /* @__PURE__ */ jsx7(Chip3, { size: "sm", variant: "soft", color: tone, children: STATUS_TEXT[status] })
558
+ ] }),
559
+ service.description && /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted", children: service.description }),
560
+ !service.configured && service.missing && service.missing.length > 0 && /* @__PURE__ */ jsxs7("p", { className: "text-xs text-muted", children: [
561
+ "Missing: ",
562
+ service.missing.join(", ")
563
+ ] })
564
+ ] }),
565
+ allowRuns && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
566
+ rateLimited && /* @__PURE__ */ jsxs7("span", { className: "text-xs text-muted", children: [
567
+ "wait ",
568
+ rateLimited.retryAfter,
569
+ "s"
570
+ ] }),
571
+ /* @__PURE__ */ jsxs7(
572
+ Button2,
573
+ {
574
+ size: "sm",
575
+ variant: "ghost",
576
+ isDisabled: !service.configured,
577
+ isPending: run.isPending,
578
+ onPress: () => run.mutate(service.key),
579
+ "aria-label": `Check ${service.name} now`,
580
+ children: [
581
+ /* @__PURE__ */ jsx7(PlayIcon, { size: 14 }),
582
+ "Check now"
583
+ ]
584
+ }
585
+ )
586
+ ] })
587
+ ] }),
588
+ service.configured && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-3 pl-5.5", children: [
589
+ /* @__PURE__ */ jsx7(Stat, { label: "Response", value: result?.probed === false ? "not probed" : formatMs(result?.latencyMs) }),
590
+ /* @__PURE__ */ jsx7(Stat, { label: "p50 / p95 24h", value: `${formatMs(stats?.p50)} / ${formatMs(stats?.p95)}` }),
591
+ /* @__PURE__ */ jsx7(Stat, { label: "Uptime 24h", value: formatPct(stats?.uptime24h) }),
592
+ /* @__PURE__ */ jsx7(Stat, { label: "Uptime 7d", value: formatPct(stats?.uptime7d) }),
593
+ stats && /* @__PURE__ */ jsx7(Sparkline, { values: stats.latency, threshold: service.slowMs }),
594
+ result && /* @__PURE__ */ jsx7(Stat, { label: "Checked", value: timeAgo(result.checkedAt) })
595
+ ] }),
596
+ result?.instances && result.instances.length > 0 && /* @__PURE__ */ jsx7("div", { className: "pl-5.5", children: /* @__PURE__ */ jsx7(InstanceTable, { instances: result.instances, stats: stats?.instances, regionNames }) }),
597
+ result?.usage && result.usage.length > 0 && /* @__PURE__ */ jsx7("div", { className: "flex flex-col gap-3 pl-5.5", children: result.usage.map((usage) => /* @__PURE__ */ jsx7(UsageBar, { usage }, usage.label)) }),
598
+ result?.metrics && Object.keys(result.metrics).length > 0 && /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-1.5 pl-5.5", children: Object.entries(result.metrics).filter(([, value]) => value !== "").map(([key, value]) => /* @__PURE__ */ jsxs7(Chip3, { size: "sm", variant: "secondary", children: [
599
+ humanize(key),
600
+ ": ",
601
+ value
602
+ ] }, key)) }),
603
+ failing && result?.error && /* @__PURE__ */ jsxs7(
604
+ "div",
605
+ {
606
+ className: `ml-5.5 rounded-lg px-3 py-2 font-mono text-xs break-words ${status === "down" ? "bg-danger/10 text-danger" : "bg-warning/10 text-warning"}`,
607
+ children: [
608
+ result.httpStatus ? `[${result.httpStatus}] ` : "",
609
+ result.error
610
+ ]
611
+ }
612
+ ),
613
+ lastFailure && /* @__PURE__ */ jsxs7("p", { className: "pl-5.5 text-xs text-muted", children: [
614
+ "Last incident ",
615
+ formatDateTime(lastFailure.at),
616
+ " (",
617
+ lastFailure.status,
618
+ ")",
619
+ lastFailure.error ? `: ${lastFailure.error}` : ""
620
+ ] }),
621
+ run.error && !rateLimited && /* @__PURE__ */ jsx7("p", { className: "pl-5.5 text-xs text-danger", children: run.error.message })
622
+ ]
623
+ }
624
+ );
625
+ }
626
+
627
+ // src/react/heroui/ServiceSection.tsx
628
+ import { Button as Button3, Card as Card2, Disclosure, Tooltip } from "@heroui/react";
629
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
630
+ import { Fragment, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
631
+ function useStoredExpanded(key, defaultExpanded) {
632
+ const storageKey = `sermon:section:${key}`;
633
+ const [expanded, setExpanded] = useState2(() => {
634
+ try {
635
+ const stored = localStorage.getItem(storageKey);
636
+ return stored === null ? defaultExpanded : stored === "expanded";
637
+ } catch {
638
+ return defaultExpanded;
639
+ }
640
+ });
641
+ const update = useCallback(
642
+ (value) => {
643
+ setExpanded(value);
644
+ try {
645
+ localStorage.setItem(storageKey, value ? "expanded" : "collapsed");
646
+ } catch {
647
+ }
648
+ },
649
+ [storageKey]
650
+ );
651
+ return [expanded, update];
652
+ }
653
+ function ServiceDots({ services }) {
654
+ return /* @__PURE__ */ jsx8("div", { className: "flex min-w-0 flex-wrap items-center justify-end gap-1", children: services.map((service) => {
655
+ const status = statusOf(service);
656
+ return /* @__PURE__ */ jsxs8(Tooltip, { delay: 100, closeDelay: 0, children: [
657
+ /* @__PURE__ */ jsx8(
658
+ Tooltip.Trigger,
659
+ {
660
+ "aria-label": `${service.name}: ${STATUS_TEXT[status]}`,
661
+ className: "flex size-4 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-focus",
662
+ children: /* @__PURE__ */ jsx8("span", { className: `size-2.5 rounded-full ${TONE_BG[STATUS_TONE[status]]}` })
663
+ }
664
+ ),
665
+ /* @__PURE__ */ jsx8(Tooltip.Content, { children: service.name })
666
+ ] }, service.key);
667
+ }) });
668
+ }
669
+ function SectionBody({ section, nested = false }) {
670
+ return /* @__PURE__ */ jsxs8(Fragment, { children: [
671
+ nested && /* @__PURE__ */ jsxs8("div", { id: groupAnchor(section.key), className: "mt-4 scroll-mt-6", children: [
672
+ /* @__PURE__ */ jsx8("h4", { className: "text-xs font-medium uppercase tracking-wide text-muted", children: section.title }),
673
+ section.description && /* @__PURE__ */ jsx8("p", { className: "text-xs text-muted", children: section.description })
674
+ ] }),
675
+ section.services.length > 0 && /* @__PURE__ */ jsx8("div", { className: "divide-y divide-border", children: section.services.map((service) => /* @__PURE__ */ jsx8(ServiceRow, { service }, service.key)) }),
676
+ section.groups.map((group) => /* @__PURE__ */ jsx8(SectionBody, { section: group, nested: true }, group.key))
677
+ ] });
678
+ }
679
+ function ServiceSection({
680
+ section,
681
+ defaultExpanded = false
682
+ }) {
683
+ const [expanded, setExpanded] = useStoredExpanded(section.key, defaultExpanded);
684
+ const services = useMemo2(() => servicesOf(section), [section]);
685
+ useEffect2(() => {
686
+ const collectGroups = (s) => [groupAnchor(s.key), ...s.groups.flatMap(collectGroups)];
687
+ const anchors = /* @__PURE__ */ new Set([...collectGroups(section), ...services.map((service) => serviceAnchor(service.key))]);
688
+ const onFocus = (event) => {
689
+ if (anchors.has(event.detail.anchor)) setExpanded(true);
690
+ };
691
+ window.addEventListener(FOCUS_EVENT, onFocus);
692
+ return () => window.removeEventListener(FOCUS_EVENT, onFocus);
693
+ }, [section, services, setExpanded]);
694
+ if (!services.length) return null;
695
+ return /* @__PURE__ */ jsx8(Card2, { id: groupAnchor(section.key), className: "scroll-mt-6", children: /* @__PURE__ */ jsxs8(Disclosure, { isExpanded: expanded, onExpandedChange: setExpanded, children: [
696
+ /* @__PURE__ */ jsxs8(Disclosure.Heading, { className: "flex flex-wrap items-start justify-between gap-x-3 gap-y-2", children: [
697
+ /* @__PURE__ */ jsxs8(Disclosure.Trigger, { className: "flex min-w-48 flex-1 cursor-pointer flex-col text-left", children: [
698
+ /* @__PURE__ */ jsx8("span", { className: "font-medium leading-snug", children: section.title }),
699
+ section.description && /* @__PURE__ */ jsx8("span", { className: "text-sm leading-snug text-muted", children: section.description })
700
+ ] }),
701
+ /* @__PURE__ */ jsxs8("div", { className: "-mt-1 ml-auto flex max-w-full items-center gap-3 sm:max-w-[50%]", children: [
702
+ /* @__PURE__ */ jsx8(ServiceDots, { services }),
703
+ /* @__PURE__ */ jsx8(
704
+ Button3,
705
+ {
706
+ isIconOnly: true,
707
+ size: "sm",
708
+ variant: "ghost",
709
+ className: "shrink-0",
710
+ "aria-label": expanded ? `Collapse ${section.title}` : `Expand ${section.title}`,
711
+ "aria-expanded": expanded,
712
+ onPress: () => setExpanded(!expanded),
713
+ children: /* @__PURE__ */ jsx8(Disclosure.Indicator, { className: "text-muted" })
714
+ }
715
+ )
716
+ ] })
717
+ ] }),
718
+ /* @__PURE__ */ jsx8(Disclosure.Content, { children: /* @__PURE__ */ jsx8(Disclosure.Body, { children: /* @__PURE__ */ jsx8("div", { className: "mt-2", children: /* @__PURE__ */ jsx8(SectionBody, { section }) }) }) })
719
+ ] }) });
720
+ }
721
+
722
+ // src/react/heroui/StatusPage.tsx
723
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
724
+ var defaultQueryClient = new QueryClient({ defaultOptions: { queries: { retry: 1 } } });
725
+ function Page({
726
+ brand,
727
+ footer,
728
+ ungroupedTitle,
729
+ defaultExpanded,
730
+ allowRuns = true,
731
+ showNav = true,
732
+ showThemeSwitcher = true,
733
+ refetchInterval
734
+ }) {
735
+ const status = useStatus({ refetchInterval });
736
+ const snapshot = status.data;
737
+ const blocks = useMemo3(() => snapshot ? buildLayout(snapshot.tree, { ungroupedTitle }) : [], [snapshot, ungroupedTitle]);
738
+ const names = useMemo3(
739
+ () => Object.fromEntries(flattenServices(snapshot?.tree ?? []).map((service) => [service.key, service.name])),
740
+ [snapshot]
741
+ );
742
+ const handledHash = useRef2(false);
743
+ useEffect3(() => {
744
+ if (handledHash.current || !snapshot) return;
745
+ handledHash.current = true;
746
+ const anchor = currentAnchor();
747
+ if (!anchor) return;
748
+ history.scrollRestoration = "manual";
749
+ focusAnchor(anchor, { smooth: false });
750
+ }, [snapshot]);
751
+ useEffect3(() => {
752
+ const onHashChange = () => {
753
+ const anchor = currentAnchor();
754
+ if (anchor) focusAnchor(anchor);
755
+ };
756
+ window.addEventListener("hashchange", onHashChange);
757
+ return () => window.removeEventListener("hashchange", onHashChange);
758
+ }, []);
759
+ return (
760
+ // On wide screens the service index is attached to the right of the main column
761
+ /* @__PURE__ */ jsxs9("div", { className: "mx-auto flex min-h-screen max-w-5xl gap-6 px-4 py-8 sm:px-6 xl:max-w-[calc(64rem+12rem+1.5rem)]", children: [
762
+ /* @__PURE__ */ jsxs9("main", { className: "flex min-w-0 flex-1 flex-col gap-6", children: [
763
+ /* @__PURE__ */ jsxs9("header", { className: "flex flex-wrap items-center justify-between gap-x-4 gap-y-3", children: [
764
+ brand ?? /* @__PURE__ */ jsx9("h1", { className: "text-lg font-semibold", children: "Status" }),
765
+ snapshot && allowRuns && /* @__PURE__ */ jsx9(RunAllButton, {})
766
+ ] }),
767
+ status.isPending && /* @__PURE__ */ jsx9("div", { className: "h-40 animate-pulse rounded-3xl bg-surface" }),
768
+ status.isError && /* @__PURE__ */ jsx9(Card3, { children: /* @__PURE__ */ jsxs9(Card3.Content, { className: "text-danger", children: [
769
+ "Could not load status: ",
770
+ status.error.message
771
+ ] }) }),
772
+ snapshot && /* @__PURE__ */ jsxs9(Fragment2, { children: [
773
+ /* @__PURE__ */ jsx9(StaleNotice, { snapshot }),
774
+ /* @__PURE__ */ jsx9(Card3, { children: /* @__PURE__ */ jsxs9(Card3.Content, { className: "flex flex-col gap-6", children: [
775
+ /* @__PURE__ */ jsx9(ScoreHeader, { snapshot }),
776
+ /* @__PURE__ */ jsx9(HistoryBars, { names })
777
+ ] }) }),
778
+ blocks.map(
779
+ (block) => block.type === "section" ? /* @__PURE__ */ jsx9(ServiceSection, { section: block.section, defaultExpanded }, block.section.key) : /* @__PURE__ */ jsxs9("div", { className: "contents", children: [
780
+ /* @__PURE__ */ jsxs9("div", { className: "mt-2 flex flex-col", children: [
781
+ /* @__PURE__ */ jsx9("h2", { className: "text-lg font-semibold leading-snug", children: block.title }),
782
+ block.description && /* @__PURE__ */ jsx9("p", { className: "text-sm leading-snug text-muted", children: block.description })
783
+ ] }),
784
+ block.sections.map((section) => /* @__PURE__ */ jsx9(ServiceSection, { section, defaultExpanded }, section.key))
785
+ ] }, block.key)
786
+ )
787
+ ] }),
788
+ /* @__PURE__ */ jsxs9("footer", { className: "flex flex-wrap items-center justify-between gap-3 pb-4 text-xs text-muted", children: [
789
+ /* @__PURE__ */ jsx9("span", { children: footer ?? (snapshot ? `Checks run every ${formatDuration(snapshot.intervalMs)} \xB7 history kept for ${formatDuration(snapshot.retentionMs)}` : "") }),
790
+ showThemeSwitcher && /* @__PURE__ */ jsx9(ThemeSwitcher, {})
791
+ ] })
792
+ ] }),
793
+ snapshot && showNav && /* @__PURE__ */ jsx9("aside", { className: "sticky top-4 hidden w-48 shrink-0 self-start xl:block", children: /* @__PURE__ */ jsx9(ServiceNav, { blocks }) })
794
+ ] })
795
+ );
796
+ }
797
+ function StatusPage(props) {
798
+ const options = useMemo3(
799
+ () => ({ regionNames: props.regionNames ?? FLY_REGIONS, allowRuns: props.allowRuns ?? true }),
800
+ [props.regionNames, props.allowRuns]
801
+ );
802
+ return /* @__PURE__ */ jsx9(QueryClientProvider, { client: props.queryClient ?? defaultQueryClient, children: /* @__PURE__ */ jsx9(StatusProvider, { baseUrl: props.baseUrl, client: props.client, children: /* @__PURE__ */ jsx9(KitOptions.Provider, { value: options, children: /* @__PURE__ */ jsx9(Page, { ...props }) }) }) });
803
+ }
804
+ export {
805
+ FLY_REGIONS,
806
+ HistoryBars,
807
+ InstanceTable,
808
+ KitOptions,
809
+ RunAllButton,
810
+ ScoreHeader,
811
+ ScoreRing,
812
+ ServiceNav,
813
+ ServiceRow,
814
+ ServiceSection,
815
+ Sparkline,
816
+ StaleNotice,
817
+ StatusPage,
818
+ ThemeSwitcher,
819
+ UsageBar
820
+ };
821
+ //# sourceMappingURL=index.js.map