@apisurf/wireui 0.1.2 → 0.1.4

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 (51) hide show
  1. package/README.md +42 -26
  2. package/bin/wireui.mjs +1 -1
  3. package/dist/nitro.json +1 -1
  4. package/dist/public/assets/environments-CpzEnLw2.js +1 -0
  5. package/dist/public/assets/feed-B8L4GK9D.js +1 -0
  6. package/dist/public/assets/globals-D_ltS-iC.css +1 -0
  7. package/dist/public/assets/history-BIXZHMWn.js +1 -0
  8. package/dist/public/assets/index-3VfD7Ktf.js +11 -0
  9. package/dist/public/assets/kv-table-Dv6o68aD.js +2 -0
  10. package/dist/public/assets/preload-helper-CIs162tl.js +3 -0
  11. package/dist/public/assets/query-DpztusaI.js +19 -0
  12. package/dist/public/assets/requests._requestId-Byq4gM_2.js +1 -0
  13. package/dist/public/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  14. package/dist/public/assets/routes-C9SXJh8z.js +1 -0
  15. package/dist/public/assets/runs._runId-CMFvexqY.js +1 -0
  16. package/dist/public/assets/scoped-CiNsRvyC.js +1 -0
  17. package/dist/public/assets/session-header-DMR_hFkk.js +1 -0
  18. package/dist/public/assets/sessions-C-F6Wmmg.js +1 -0
  19. package/dist/public/assets/split-Ddiw3mOT.js +1 -0
  20. package/dist/public/assets/tabs-C1EijW_Z.js +2 -0
  21. package/dist/server/_ssr/{api-Dw1Zb_PK.mjs → api-DUem3i24.mjs} +28 -17
  22. package/dist/server/_ssr/environments-BFBtf7iL.mjs +146 -0
  23. package/dist/server/_ssr/feed-BgweoqQ4.mjs +108 -0
  24. package/dist/server/_ssr/history-57VNXvbE.mjs +195 -0
  25. package/dist/server/_ssr/{kv-table-DQUkoItO.mjs → kv-table-BONnSOLb.mjs} +2 -2
  26. package/dist/server/_ssr/{router-CPcgc_kh.mjs → prefs-iPvYd_Vz.mjs} +246 -1551
  27. package/dist/server/_ssr/{query-D4so9xiY.mjs → query-D6nNoSN3.mjs} +7 -6
  28. package/dist/server/_ssr/{requests._requestId-BFukRwqL.mjs → requests._requestId-bXoTBGVj.mjs} +7 -23
  29. package/dist/server/_ssr/router-ClsXvW-b.mjs +1068 -0
  30. package/dist/server/_ssr/{routes-DaV9vGHn.mjs → routes-CrmwJhiA.mjs} +18 -8
  31. package/dist/server/_ssr/runs._runId-DM5Y8e6b.mjs +850 -0
  32. package/dist/server/_ssr/{scoped-CnQ56K8h.mjs → scoped-CNX8KOhL.mjs} +2 -2
  33. package/dist/server/_ssr/session-header-BYy6LaSU.mjs +153 -0
  34. package/dist/server/_ssr/{collections-CqidTQpw.mjs → sessions-wsD0b1zm.mjs} +35 -72
  35. package/dist/server/_ssr/{split-DWrMIs7K.mjs → split-qGbYHKc9.mjs} +2 -2
  36. package/dist/server/_ssr/ssr.mjs +10 -14
  37. package/dist/server/_tanstack-start-manifest_v-Ag2qLfrP.mjs +79 -0
  38. package/dist/server/index.mjs +100 -51
  39. package/package.json +8 -6
  40. package/dist/public/assets/collections-CLHcfRfi.js +0 -1
  41. package/dist/public/assets/globals-CZiNFGaK.css +0 -1
  42. package/dist/public/assets/index-D5VFd_3H.js +0 -14
  43. package/dist/public/assets/kv-table-oB5gcq0i.js +0 -2
  44. package/dist/public/assets/query-BKAhHNIi.js +0 -19
  45. package/dist/public/assets/requests._requestId-Bu7E2eoI.js +0 -1
  46. package/dist/public/assets/routes-DDY2CfzA.js +0 -1
  47. package/dist/public/assets/runs._runId-CzwtVjYq.js +0 -1
  48. package/dist/public/assets/scoped-DB8raIWx.js +0 -1
  49. package/dist/public/assets/split-Br1RX2I4.js +0 -1
  50. package/dist/server/_ssr/runs._runId-Df_LmPHa.mjs +0 -836
  51. package/dist/server/_tanstack-start-manifest_v-CARKjHfs.mjs +0 -50
@@ -0,0 +1,850 @@
1
+ import { __toESM } from "../_runtime.mjs";
2
+ import { require_jsx_runtime, require_react, useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
3
+ import { Callout, Card, Dot, Empty, FilterInput, IconAlert, IconButton, IconCheckCircle, IconChevronLeft, IconChevronRight, IconXCircle, MethodBadge, Metric, Pill, SectionTitle, Segmented, StatStrip, StatusBadge, TabList, css, cx, formatBytes, formatCount, formatDateTime, formatMs, formatOffset, isFailure, mono, pagePad, plural, requestErrorsHint, runTone, runVerdict, scrollPane, statusTone, tabKey, table, toneText, truncate, useNow, useTab, useUnreadable } from "./prefs-iPvYd_Vz.mjs";
4
+ import { Route } from "./router-ClsXvW-b.mjs";
5
+ import { Code, KvTable } from "./kv-table-BONnSOLb.mjs";
6
+ import { SessionHeader } from "./session-header-BYy6LaSU.mjs";
7
+ //#region node_modules/.nitro/vite/services/ssr/assets/runs._runId-DM5Y8e6b.js
8
+ var import_react = /* @__PURE__ */ __toESM(require_react());
9
+ var import_jsx_runtime = require_jsx_runtime();
10
+ function RunPage() {
11
+ const { run, tags, requests, checks, analytics, neighbours } = Route.useLoaderData();
12
+ const { tab = "timeline" } = Route.useSearch();
13
+ const navigate = useNavigate({ from: Route.fullPath });
14
+ const unreadable = useUnreadable();
15
+ const tone = runTone(run);
16
+ useTab({
17
+ key: tabKey.run(run.id),
18
+ title: `${run.file} #${run.id}`,
19
+ icon: "run",
20
+ failed: tone === "bad"
21
+ });
22
+ const go = import_react.useCallback((id) => {
23
+ if (id == null) return;
24
+ navigate({
25
+ to: "/runs/$runId",
26
+ params: { runId: String(id) },
27
+ search: (s) => ({ tab: s.tab })
28
+ });
29
+ }, [navigate]);
30
+ import_react.useEffect(() => {
31
+ const onKey = (event) => {
32
+ const target = event.target;
33
+ if (target && /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)) return;
34
+ if (event.key === "[") go(neighbours.prev);
35
+ if (event.key === "]") go(neighbours.next);
36
+ };
37
+ window.addEventListener("keydown", onKey);
38
+ return () => window.removeEventListener("keydown", onKey);
39
+ }, [
40
+ go,
41
+ neighbours.prev,
42
+ neighbours.next
43
+ ]);
44
+ if (unreadable) return unreadable;
45
+ const failedChecks = checks.filter((c) => c.status === "fail").length;
46
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
47
+ className: css({
48
+ display: "flex",
49
+ flexDir: "column",
50
+ h: "full",
51
+ minH: "0"
52
+ }),
53
+ children: [
54
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SessionHeader, {
55
+ script: run,
56
+ linked: true,
57
+ tone,
58
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunHeader, {
59
+ run,
60
+ neighbours,
61
+ go
62
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabList, {
63
+ items: [
64
+ {
65
+ id: "timeline",
66
+ label: "Timeline"
67
+ },
68
+ {
69
+ id: "requests",
70
+ label: "Requests",
71
+ count: requests.length
72
+ },
73
+ {
74
+ id: "checks",
75
+ label: "Checks",
76
+ count: checks.length ? `${checks.length - failedChecks}/${checks.length}` : 0,
77
+ tone: failedChecks ? "bad" : checks.length ? "ok" : void 0
78
+ },
79
+ {
80
+ id: "details",
81
+ label: "Details",
82
+ count: tags.length || void 0
83
+ }
84
+ ],
85
+ value: tab,
86
+ onSelect: (next) => void navigate({ search: { tab: next === "timeline" ? void 0 : next } })
87
+ })]
88
+ }),
89
+ tab === "timeline" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestsTab, {
90
+ run,
91
+ requests,
92
+ checks
93
+ }) : null,
94
+ tab === "requests" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestsTab, {
95
+ run,
96
+ requests,
97
+ checks: [],
98
+ analytics
99
+ }) : null,
100
+ tab === "checks" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChecksTab, {
101
+ run,
102
+ checks
103
+ }) : null,
104
+ tab === "details" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DetailsTab, {
105
+ run,
106
+ tags
107
+ }) : null
108
+ ]
109
+ });
110
+ }
111
+ function RunHeader({ run, neighbours, go }) {
112
+ const now = useNow(Date.now(), 6e4);
113
+ const tone = runTone(run);
114
+ const sep = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
115
+ className: css({ color: "faint" }),
116
+ children: "·"
117
+ });
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
119
+ className: css({
120
+ display: "flex",
121
+ flexDir: "column",
122
+ gap: "2.5",
123
+ px: "5",
124
+ pt: "1.5",
125
+ pb: "2"
126
+ }),
127
+ children: [
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
129
+ className: css({
130
+ display: "flex",
131
+ alignItems: "center",
132
+ gap: "3"
133
+ }),
134
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
135
+ className: css({
136
+ minW: "0",
137
+ flex: "1",
138
+ display: "flex",
139
+ alignItems: "center",
140
+ gap: "2",
141
+ flexWrap: "wrap",
142
+ fontSize: "12px",
143
+ color: "muted"
144
+ }),
145
+ children: [
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dot, { tone }),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
148
+ className: css({
149
+ fontSize: "18px",
150
+ fontWeight: "600",
151
+ m: "0",
152
+ color: "fg"
153
+ }),
154
+ children: ["Run ", /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
155
+ className: css({ fontFamily: "mono" }),
156
+ children: ["#", run.id]
157
+ })]
158
+ }),
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
160
+ tone,
161
+ title: run.error_count ? requestErrorsHint(run.error_count) : void 0,
162
+ children: runVerdict(run)
163
+ }),
164
+ run.env ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, { children: run.env }) : null,
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatDateTime(now, run.started_at) }),
166
+ sep,
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["node ", run.node_version] }),
168
+ run.label && run.label !== run.file ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [sep, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: run.label })] }) : null
169
+ ]
170
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
171
+ className: css({
172
+ display: "flex",
173
+ gap: "1",
174
+ flexShrink: 0
175
+ }),
176
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
177
+ label: "Older run of this script ([)",
178
+ disabled: neighbours.prev == null,
179
+ onClick: () => go(neighbours.prev),
180
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronLeft, {})
181
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
182
+ label: "Newer run of this script (])",
183
+ disabled: neighbours.next == null,
184
+ onClick: () => go(neighbours.next),
185
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronRight, {})
186
+ })]
187
+ })]
188
+ }),
189
+ run.exit_status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Callout, {
190
+ tone: "bad",
191
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconAlert, { size: 15 }),
192
+ title: run.error_name ?? "The script threw",
193
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
194
+ className: mono,
195
+ children: run.error_message
196
+ })
197
+ }) : null,
198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunMetrics, { run })
199
+ ]
200
+ });
201
+ }
202
+ /** What the run cost, in one strip under its title. */
203
+ function RunMetrics({ run }) {
204
+ const wall = run.finished_at ? run.finished_at - run.started_at : null;
205
+ const httpCount = run.request_count - run.connection_count;
206
+ const checks = run.check_count;
207
+ const failedChecks = run.failed_check_count;
208
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatStrip, { items: [
209
+ {
210
+ label: "Wall time",
211
+ value: formatMs(wall),
212
+ title: run.finished_at ? "Start to finish" : "Never finished"
213
+ },
214
+ {
215
+ label: "Requests",
216
+ value: run.connection_count ? `${formatCount(httpCount)} + ${plural(run.connection_count, "connection")}` : formatCount(httpCount),
217
+ title: `${formatMs(run.total_duration_ms)} in flight`
218
+ },
219
+ {
220
+ label: "Failed requests",
221
+ value: formatCount(run.error_count),
222
+ tone: run.error_count ? "warn" : void 0,
223
+ title: "Errors and ≥ 400 responses — they don't fail the run"
224
+ },
225
+ {
226
+ label: "Checks",
227
+ value: checks ? `${checks - failedChecks}/${checks}` : "—",
228
+ tone: failedChecks ? "bad" : checks ? "ok" : void 0,
229
+ title: checks ? failedChecks ? `${failedChecks} failed` : "All passed" : "None recorded"
230
+ },
231
+ {
232
+ label: "Data",
233
+ value: formatBytes(run.total_bytes),
234
+ title: "Sent + received"
235
+ }
236
+ ] });
237
+ }
238
+ var BAR = {
239
+ ok: css({ bg: "ok" }),
240
+ info: css({ bg: "info" }),
241
+ warn: css({ bg: "warn" }),
242
+ bad: css({ bg: "bad" }),
243
+ neutral: css({ bg: "faint" })
244
+ };
245
+ function RequestsTab({ run, requests, checks, analytics }) {
246
+ const navigate = useNavigate();
247
+ const [filter, setFilter] = import_react.useState("");
248
+ const [show, setShow] = import_react.useState("all");
249
+ const [host, setHost] = import_react.useState(null);
250
+ const [order, setOrder] = import_react.useState("time");
251
+ const needle = filter.trim().toLowerCase();
252
+ const rows = import_react.useMemo(() => {
253
+ const matches = (text) => !needle || text.toLowerCase().includes(needle);
254
+ const shown = requests.filter((row) => (show === "all" || isFailure(row)) && (host == null || row.host === host) && matches(`${row.method} ${row.host}${row.path} ${row.status ?? ""}`));
255
+ const merged = (order === "slowest" ? shown.toSorted((a, b) => b.duration_ms - a.duration_ms) : shown).map((row) => ({
256
+ type: "request",
257
+ row
258
+ }));
259
+ if (checks.length === 0) return merged;
260
+ for (const check of checks) {
261
+ if (show === "failed" && check.status !== "fail") continue;
262
+ if (!matches(`${check.name} ${check.message ?? ""}`)) continue;
263
+ merged.push({
264
+ type: "check",
265
+ row: check
266
+ });
267
+ }
268
+ return merged.toSorted((a, b) => a.row.ts - b.row.ts);
269
+ }, [
270
+ requests,
271
+ checks,
272
+ needle,
273
+ show,
274
+ host,
275
+ order
276
+ ]);
277
+ const start = run.started_at;
278
+ const span = Math.max(run.finished_at ?? 0, ...requests.map((r) => r.ts + r.duration_ms), ...checks.map((c) => c.ts), start + 1) - start;
279
+ const failures = requests.filter(isFailure).length + checks.filter((c) => c.status === "fail").length;
280
+ if (requests.length === 0 && checks.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, { title: "This run made no requests" });
281
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
282
+ className: css({
283
+ display: "flex",
284
+ flexDir: "column",
285
+ flex: "1",
286
+ minH: "0"
287
+ }),
288
+ children: [
289
+ analytics && analytics.total > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestInsights, {
290
+ run,
291
+ analytics,
292
+ host,
293
+ onHost: setHost
294
+ }) : null,
295
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
296
+ className: css({
297
+ display: "flex",
298
+ gap: "2",
299
+ px: "5",
300
+ py: "2",
301
+ alignItems: "center",
302
+ flexShrink: 0,
303
+ flexWrap: "wrap"
304
+ }),
305
+ children: [
306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterInput, {
307
+ className: css({ w: "280px" }),
308
+ placeholder: checks.length ? "Filter by method, path, status or check" : "Filter by method, path or status",
309
+ value: filter,
310
+ onChange: (e) => setFilter(e.target.value)
311
+ }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Segmented, {
313
+ items: [{
314
+ id: "all",
315
+ label: `All ${requests.length + checks.length}`
316
+ }, {
317
+ id: "failed",
318
+ label: `Failed ${failures}`
319
+ }],
320
+ value: show,
321
+ onSelect: setShow
322
+ }),
323
+ analytics ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Segmented, {
324
+ items: [{
325
+ id: "time",
326
+ label: "In order"
327
+ }, {
328
+ id: "slowest",
329
+ label: "Slowest first"
330
+ }],
331
+ value: order,
332
+ onSelect: setOrder
333
+ }) : null,
334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
335
+ className: css({
336
+ ml: "auto",
337
+ color: "faint",
338
+ fontSize: "12px"
339
+ }),
340
+ children: ["Timeline spans ", formatMs(span)]
341
+ })
342
+ ]
343
+ }),
344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
345
+ className: scrollPane,
346
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
347
+ className: table,
348
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
350
+ className: "num",
351
+ style: { width: 40 },
352
+ children: "#"
353
+ }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Request" }),
355
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Status" }),
356
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
357
+ className: "num",
358
+ children: "Time"
359
+ }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
361
+ className: "num",
362
+ children: "Size"
363
+ }),
364
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
365
+ style: { width: "32%" },
366
+ children: "Timeline"
367
+ })
368
+ ] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tbody", { children: [rows.map((item) => item.type === "check" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckLine, {
369
+ check: item.row,
370
+ start,
371
+ span
372
+ }, `c${item.row.id}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
373
+ "data-link": true,
374
+ onClick: () => void navigate({
375
+ to: "/requests/$requestId",
376
+ params: { requestId: String(item.row.id) }
377
+ }),
378
+ children: [
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
380
+ className: cx("num", mono, css({ color: "faint" })),
381
+ children: item.row.seq + 1
382
+ }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
384
+ className: css({
385
+ maxW: "0",
386
+ w: "full"
387
+ }),
388
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
389
+ className: css({
390
+ display: "flex",
391
+ alignItems: "center",
392
+ gap: "2",
393
+ minW: "0"
394
+ }),
395
+ children: [
396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodBadge, {
397
+ method: item.row.method,
398
+ kind: item.row.kind,
399
+ width: "list"
400
+ }),
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
402
+ className: cx(truncate, mono),
403
+ title: `${item.row.host}${item.row.path}`,
404
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
405
+ className: css({ color: "faint" }),
406
+ children: item.row.host
407
+ }), item.row.path]
408
+ }),
409
+ item.row.kind !== "http" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
410
+ className: css({
411
+ color: "faint",
412
+ fontSize: "11px",
413
+ whiteSpace: "nowrap"
414
+ }),
415
+ children: plural(item.row.message_count, "message")
416
+ }) : null
417
+ ]
418
+ })
419
+ }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBadge, {
421
+ status: item.row.status,
422
+ error: item.row.error_name
423
+ }) }),
424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
425
+ className: cx("num", mono),
426
+ children: formatMs(item.row.duration_ms)
427
+ }),
428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
429
+ className: cx("num", mono, css({ color: "muted" })),
430
+ children: formatBytes(item.row.response_bytes)
431
+ }),
432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
433
+ className: css({
434
+ position: "relative",
435
+ h: "8px"
436
+ }),
437
+ title: `${formatOffset(item.row.ts - start)} · ${formatMs(item.row.duration_ms)}`,
438
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
439
+ className: cx(css({
440
+ position: "absolute",
441
+ top: "0",
442
+ bottom: "0",
443
+ rounded: "full",
444
+ minW: "2px"
445
+ }), BAR[statusTone(item.row.status, item.row.error_name)]),
446
+ style: {
447
+ left: `${(item.row.ts - start) / span * 100}%`,
448
+ width: `${item.row.duration_ms / span * 100}%`,
449
+ opacity: item.row.kind === "http" ? 1 : .5
450
+ }
451
+ })
452
+ }) })
453
+ ]
454
+ }, item.row.id)), rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
455
+ colSpan: 6,
456
+ className: css({
457
+ textAlign: "center",
458
+ color: "faint",
459
+ py: "6"
460
+ }),
461
+ children: checks.length ? "Nothing matches." : "No requests match."
462
+ }) }) : null] })]
463
+ })
464
+ })
465
+ ]
466
+ });
467
+ }
468
+ function CheckLine({ check, start, span }) {
469
+ const pass = check.status === "pass";
470
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
472
+ className: "num",
473
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
474
+ className: cx(css({
475
+ display: "inline-flex",
476
+ verticalAlign: "middle"
477
+ }), toneText(pass ? "ok" : "bad")),
478
+ title: pass ? "Passed" : "Failed",
479
+ children: pass ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCheckCircle, { size: 13 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconXCircle, { size: 13 })
480
+ })
481
+ }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
483
+ colSpan: 4,
484
+ className: css({ maxW: "0" }),
485
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
486
+ className: css({
487
+ display: "flex",
488
+ alignItems: "center",
489
+ gap: "2",
490
+ minW: "0",
491
+ fontSize: "12px"
492
+ }),
493
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
494
+ className: css({
495
+ display: "inline-block",
496
+ w: "46px",
497
+ flexShrink: 0,
498
+ fontFamily: "mono",
499
+ fontSize: "10.5px",
500
+ fontWeight: "700",
501
+ letterSpacing: "0.02em",
502
+ color: "faint"
503
+ }),
504
+ children: check.mode === "assert" ? "ASSERT" : "CHECK"
505
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
506
+ className: cx(truncate, pass ? css({ color: "muted" }) : css({
507
+ color: "bad",
508
+ fontWeight: "500"
509
+ })),
510
+ children: [check.name, !pass && check.message ? ` — ${check.message}` : ""]
511
+ })]
512
+ })
513
+ }),
514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
515
+ className: css({
516
+ position: "relative",
517
+ h: "8px"
518
+ }),
519
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
520
+ className: cx(css({
521
+ position: "absolute",
522
+ top: "-1px",
523
+ w: "2px",
524
+ h: "10px",
525
+ rounded: "full"
526
+ }), pass ? BAR.ok : BAR.bad),
527
+ style: { left: `${(check.ts - start) / span * 100}%` }
528
+ })
529
+ }) })
530
+ ] });
531
+ }
532
+ function ChecksTab({ run, checks }) {
533
+ if (checks.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Empty, {
534
+ title: "No checks in this run",
535
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCheckCircle, { size: 22 }),
536
+ children: [
537
+ "A script records expectations with ",
538
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "check()" }),
539
+ " and ",
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "assert()" }),
541
+ " from",
542
+ " ",
543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "@apisurf/wire/kit" }),
544
+ "."
545
+ ]
546
+ });
547
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
548
+ className: scrollPane,
549
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
550
+ className: pagePad,
551
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: checks.map((check) => {
552
+ const pass = check.status === "pass";
553
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
554
+ className: css({
555
+ display: "flex",
556
+ gap: "2.5",
557
+ px: "3",
558
+ py: "2",
559
+ borderBottom: "1px solid",
560
+ borderColor: "line",
561
+ _last: { borderBottom: "none" }
562
+ }),
563
+ children: [
564
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
565
+ className: cx(css({ pt: "1px" }), toneText(pass ? "ok" : "bad")),
566
+ children: pass ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCheckCircle, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconXCircle, { size: 16 })
567
+ }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
569
+ className: css({
570
+ flex: "1",
571
+ minW: "0"
572
+ }),
573
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
574
+ className: css({
575
+ display: "flex",
576
+ gap: "2",
577
+ alignItems: "center"
578
+ }),
579
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
580
+ className: css({ fontWeight: "500" }),
581
+ children: check.name
582
+ }), check.mode === "assert" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
583
+ tone: pass ? "neutral" : "bad",
584
+ children: "assert"
585
+ }) : null]
586
+ }), !pass && check.message ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
587
+ className: cx(mono, css({
588
+ color: "bad",
589
+ mt: "0.5",
590
+ wordBreak: "break-word"
591
+ })),
592
+ children: check.message
593
+ }) : null]
594
+ }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
596
+ className: cx(mono, css({
597
+ color: "faint",
598
+ whiteSpace: "nowrap"
599
+ })),
600
+ children: formatOffset(check.ts - run.started_at)
601
+ })
602
+ ]
603
+ }, check.id);
604
+ }) })
605
+ })
606
+ });
607
+ }
608
+ var MIX_TONE = {
609
+ "1xx": "info",
610
+ "2xx": "ok",
611
+ "3xx": "info",
612
+ "4xx": "warn",
613
+ "5xx": "bad",
614
+ error: "bad"
615
+ };
616
+ function RequestInsights({ run, analytics, host, onHost }) {
617
+ const latencyHint = `Over ${plural(analytics.latencySamples, "HTTP request")}. A WebSocket or SSE connection's duration is how long it stayed open, so it is left out.` + (run.connection_count ? ` This run opened ${plural(run.connection_count, "connection")}.` : "");
618
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
619
+ className: css({
620
+ display: "flex",
621
+ flexDir: "column",
622
+ gap: "2",
623
+ px: "5",
624
+ pt: "3",
625
+ pb: "1",
626
+ flexShrink: 0
627
+ }),
628
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
629
+ className: css({
630
+ display: "flex",
631
+ flexWrap: "wrap",
632
+ alignItems: "center",
633
+ rowGap: "2",
634
+ columnGap: "5"
635
+ }),
636
+ children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
638
+ label: "p50",
639
+ value: formatMs(analytics.p50),
640
+ title: latencyHint
641
+ }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
643
+ label: "p95",
644
+ value: formatMs(analytics.p95),
645
+ title: latencyHint
646
+ }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
648
+ label: "p99",
649
+ value: formatMs(analytics.p99),
650
+ title: latencyHint
651
+ }),
652
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusMix, { analytics })
653
+ ]
654
+ }), analytics.byHost.length > 1 || host != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
655
+ className: css({
656
+ display: "flex",
657
+ flexWrap: "wrap",
658
+ alignItems: "center",
659
+ gap: "1.5"
660
+ }),
661
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
662
+ className: css({
663
+ color: "muted",
664
+ fontSize: "12px",
665
+ mr: "1"
666
+ }),
667
+ children: "Hosts"
668
+ }), analytics.byHost.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HostChip, {
669
+ row,
670
+ pressed: host === row.host,
671
+ onClick: () => onHost(host === row.host ? null : row.host)
672
+ }, row.host))]
673
+ }) : null]
674
+ });
675
+ }
676
+ function StatusMix({ analytics }) {
677
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
678
+ className: css({
679
+ display: "flex",
680
+ alignItems: "center",
681
+ gap: "3",
682
+ minW: "0"
683
+ }),
684
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
685
+ className: css({
686
+ display: "flex",
687
+ gap: "2px",
688
+ h: "8px",
689
+ w: "160px",
690
+ flexShrink: 0
691
+ }),
692
+ role: "img",
693
+ "aria-label": "Status mix",
694
+ children: analytics.statusMix.map((mix) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
695
+ title: `${mix.status_class}: ${mix.count}`,
696
+ className: cx(css({
697
+ rounded: "full",
698
+ minW: "4px"
699
+ }), BAR[MIX_TONE[mix.status_class] ?? "neutral"]),
700
+ style: { flexGrow: mix.count }
701
+ }, mix.status_class))
702
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
703
+ className: css({
704
+ display: "flex",
705
+ gap: "3",
706
+ flexWrap: "wrap",
707
+ fontSize: "12px"
708
+ }),
709
+ children: analytics.statusMix.map((mix) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
710
+ className: css({
711
+ display: "inline-flex",
712
+ alignItems: "center",
713
+ gap: "1"
714
+ }),
715
+ title: `${Math.round(mix.count / analytics.total * 100)}% of requests`,
716
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
717
+ className: cx(mono, toneText(MIX_TONE[mix.status_class] ?? "neutral")),
718
+ children: mix.status_class
719
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
720
+ className: css({ color: "muted" }),
721
+ children: mix.count
722
+ })]
723
+ }, mix.status_class))
724
+ })]
725
+ });
726
+ }
727
+ function HostChip({ row, pressed, onClick }) {
728
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
729
+ type: "button",
730
+ "aria-pressed": pressed,
731
+ onClick,
732
+ title: `${plural(row.count, "call")} · p95 ${formatMs(row.p95)}${row.errors ? ` · ${plural(row.errors, "error")}` : ""} — click to ${pressed ? "show every host" : "show only this host"}`,
733
+ className: css({
734
+ display: "inline-flex",
735
+ alignItems: "center",
736
+ gap: "1.5",
737
+ h: "24px",
738
+ px: "2",
739
+ rounded: "md",
740
+ border: "1px solid",
741
+ borderColor: "line",
742
+ bg: "raised",
743
+ color: "fg",
744
+ cursor: "pointer",
745
+ fontSize: "12px",
746
+ _hover: {
747
+ borderColor: "lineStrong",
748
+ bg: "hover"
749
+ },
750
+ "&[aria-pressed=true]": {
751
+ borderColor: "accent",
752
+ bg: "accentSoft",
753
+ color: "accent"
754
+ }
755
+ }),
756
+ children: [
757
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
758
+ className: mono,
759
+ children: row.host
760
+ }),
761
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
762
+ className: css({
763
+ color: "muted",
764
+ fontVariantNumeric: "tabular-nums"
765
+ }),
766
+ children: row.count
767
+ }),
768
+ row.errors ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
769
+ className: css({
770
+ color: "warn",
771
+ fontVariantNumeric: "tabular-nums"
772
+ }),
773
+ children: [row.errors, "✕"]
774
+ }) : null
775
+ ]
776
+ });
777
+ }
778
+ function DetailsTab({ run, tags }) {
779
+ const now = useNow(Date.now(), 6e4);
780
+ const rows = [
781
+ {
782
+ name: "id",
783
+ value: String(run.id)
784
+ },
785
+ {
786
+ name: "uid",
787
+ value: run.uid
788
+ },
789
+ {
790
+ name: "label",
791
+ value: run.label ?? "—"
792
+ },
793
+ {
794
+ name: "entry",
795
+ value: run.entry
796
+ },
797
+ {
798
+ name: "cwd",
799
+ value: run.cwd
800
+ },
801
+ {
802
+ name: "env",
803
+ value: run.env ?? "—"
804
+ },
805
+ {
806
+ name: "node",
807
+ value: run.node_version
808
+ },
809
+ {
810
+ name: "started",
811
+ value: formatDateTime(now, run.started_at)
812
+ },
813
+ {
814
+ name: "finished",
815
+ value: run.finished_at ? formatDateTime(now, run.finished_at) : "—"
816
+ },
817
+ {
818
+ name: "exit status",
819
+ value: run.exit_status ?? "—"
820
+ }
821
+ ];
822
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
823
+ className: scrollPane,
824
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
825
+ className: pagePad,
826
+ children: [
827
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { children: "Run" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KvTable, {
828
+ rows,
829
+ empty: "",
830
+ filterable: false,
831
+ nameLabel: "Field"
832
+ }) })] }),
833
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { children: "Tags" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KvTable, {
834
+ rows: tags.map((t) => ({
835
+ name: t.key,
836
+ value: t.value
837
+ })),
838
+ empty: "No tags — add them with wire execute --tag key=value",
839
+ filterable: false
840
+ }) })] }),
841
+ run.entry_source ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { children: "Inline source" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
842
+ className: css({ bg: "well" }),
843
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Code, { text: run.entry_source })
844
+ }) })] }) : null
845
+ ]
846
+ })
847
+ });
848
+ }
849
+ //#endregion
850
+ export { RunPage as component };