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.
- package/LICENSE +21 -0
- package/README.md +239 -2
- package/dist/api/index.cjs +174 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +77 -0
- package/dist/api/index.d.ts +77 -0
- package/dist/api/index.js +171 -0
- package/dist/api/index.js.map +1 -0
- package/dist/chunk-2M4UBCDM.js +178 -0
- package/dist/chunk-2M4UBCDM.js.map +1 -0
- package/dist/chunk-AH372HLN.js +98 -0
- package/dist/chunk-AH372HLN.js.map +1 -0
- package/dist/chunk-APC2HVSI.js +154 -0
- package/dist/chunk-APC2HVSI.js.map +1 -0
- package/dist/chunk-D7BBJTYU.cjs +335 -0
- package/dist/chunk-D7BBJTYU.cjs.map +1 -0
- package/dist/chunk-ELV4ZU6G.cjs +51 -0
- package/dist/chunk-ELV4ZU6G.cjs.map +1 -0
- package/dist/chunk-EMP7XVKC.js +334 -0
- package/dist/chunk-EMP7XVKC.js.map +1 -0
- package/dist/chunk-I3346MUL.js +49 -0
- package/dist/chunk-I3346MUL.js.map +1 -0
- package/dist/chunk-NP3YYVRZ.cjs +163 -0
- package/dist/chunk-NP3YYVRZ.cjs.map +1 -0
- package/dist/chunk-PARE6EUZ.cjs +184 -0
- package/dist/chunk-PARE6EUZ.cjs.map +1 -0
- package/dist/chunk-XHXEKB3Q.cjs +241 -0
- package/dist/chunk-XHXEKB3Q.cjs.map +1 -0
- package/dist/chunk-XPXIDJUU.js +229 -0
- package/dist/chunk-XPXIDJUU.js.map +1 -0
- package/dist/chunk-ZI442ZA5.cjs +100 -0
- package/dist/chunk-ZI442ZA5.cjs.map +1 -0
- package/dist/config-BEbP-uLf.d.cts +125 -0
- package/dist/config-w1tm1bdu.d.ts +125 -0
- package/dist/generic-BJmlgtUM.d.ts +31 -0
- package/dist/generic-lmrGxMDC.d.cts +31 -0
- package/dist/index.cjs +407 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +330 -0
- package/dist/index.js.map +1 -0
- package/dist/presets/index.cjs +1012 -0
- package/dist/presets/index.cjs.map +1 -0
- package/dist/presets/index.d.cts +516 -0
- package/dist/presets/index.d.ts +516 -0
- package/dist/presets/index.js +971 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/react/heroui/index.cjs +822 -0
- package/dist/react/heroui/index.cjs.map +1 -0
- package/dist/react/heroui/index.d.cts +122 -0
- package/dist/react/heroui/index.d.ts +122 -0
- package/dist/react/heroui/index.js +821 -0
- package/dist/react/heroui/index.js.map +1 -0
- package/dist/react/heroui/styles.css +11 -0
- package/dist/react/index.cjs +71 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +53 -0
- package/dist/react/index.d.ts +53 -0
- package/dist/react/index.js +70 -0
- package/dist/react/index.js.map +1 -0
- package/dist/status-DsWzgb9Q.d.cts +146 -0
- package/dist/status-zd04ZQ98.d.ts +146 -0
- package/dist/store-DybhDphS.d.cts +77 -0
- package/dist/store-_GuzSP0z.d.ts +77 -0
- package/dist/stores/mongodb.cjs +99 -0
- package/dist/stores/mongodb.cjs.map +1 -0
- package/dist/stores/mongodb.d.cts +40 -0
- package/dist/stores/mongodb.d.ts +40 -0
- package/dist/stores/mongodb.js +97 -0
- package/dist/stores/mongodb.js.map +1 -0
- package/dist/stores/redis.cjs +23 -0
- package/dist/stores/redis.cjs.map +1 -0
- package/dist/stores/redis.d.cts +31 -0
- package/dist/stores/redis.d.ts +31 -0
- package/dist/stores/redis.js +21 -0
- package/dist/stores/redis.js.map +1 -0
- package/dist/stores/sql.cjs +164 -0
- package/dist/stores/sql.cjs.map +1 -0
- package/dist/stores/sql.d.cts +39 -0
- package/dist/stores/sql.d.ts +39 -0
- package/dist/stores/sql.js +162 -0
- package/dist/stores/sql.js.map +1 -0
- package/dist/stores/upstash.cjs +27 -0
- package/dist/stores/upstash.cjs.map +1 -0
- package/dist/stores/upstash.d.cts +19 -0
- package/dist/stores/upstash.d.ts +19 -0
- package/dist/stores/upstash.js +25 -0
- package/dist/stores/upstash.js.map +1 -0
- package/dist/testing/index.cjs +209 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +66 -0
- package/dist/testing/index.d.ts +66 -0
- package/dist/testing/index.js +205 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tree-BXbXfjF1.d.cts +236 -0
- package/dist/tree-BXbXfjF1.d.ts +236 -0
- package/dist/types-CctvUAs2.d.cts +192 -0
- package/dist/types-CctvUAs2.d.ts +192 -0
- 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
|