@datum-cloud/datum-ui 2.0.0 → 2.3.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 (80) hide show
  1. package/dist/app-navigation/index.mjs +2 -2
  2. package/dist/{app-navigation-CNig4Maj.mjs → app-navigation--tEBCrfl.mjs} +180 -164
  3. package/dist/autocomplete/index.mjs +1 -1
  4. package/dist/{autocomplete-DM6JYV4W.mjs → autocomplete-DZ_HSBak.mjs} +1 -1
  5. package/dist/autosearch/index.mjs +1 -1
  6. package/dist/{calendar-date-picker-AzvSqjeS.mjs → calendar-date-picker-ynSGADfR.mjs} +1 -1
  7. package/dist/components/base/sheet/sheet.d.ts +2 -1
  8. package/dist/components/base/sidebar/context.d.ts +3 -0
  9. package/dist/components/base/sidebar/shell.d.ts +2 -2
  10. package/dist/components/features/app-navigation/nav-menu/index.d.ts +1 -1
  11. package/dist/components/features/app-navigation/nav-menu/nav-menu-context.d.ts +4 -0
  12. package/dist/components/features/app-navigation/nav-menu/nav-menu-item.d.ts +3 -0
  13. package/dist/components/features/app-navigation/nav-menu/types.d.ts +18 -0
  14. package/dist/components/features/date-time/date-time.d.ts +6 -0
  15. package/dist/components/features/date-time/formatters.d.ts +13 -0
  16. package/dist/components/features/date-time/index.d.ts +3 -0
  17. package/dist/components/features/date-time/types.d.ts +47 -0
  18. package/dist/components/features/index.d.ts +1 -0
  19. package/dist/components/features/logs/components/detail.d.ts +3 -0
  20. package/dist/components/features/logs/components/explorer.d.ts +3 -0
  21. package/dist/components/features/logs/components/filters.d.ts +11 -0
  22. package/dist/components/features/logs/components/root.d.ts +2 -0
  23. package/dist/components/features/logs/components/status-badge.d.ts +7 -0
  24. package/dist/components/features/logs/components/table.d.ts +3 -0
  25. package/dist/components/features/logs/components/toolbar.d.ts +12 -0
  26. package/dist/components/features/logs/fixtures/index.d.ts +2 -0
  27. package/dist/components/features/logs/hooks/use-controllable-state.d.ts +5 -0
  28. package/dist/components/features/logs/hooks/use-logs.d.ts +3 -0
  29. package/dist/components/features/logs/index.d.ts +4 -0
  30. package/dist/components/features/logs/logs.d.ts +18 -0
  31. package/dist/components/features/logs/types.d.ts +105 -0
  32. package/dist/components/features/logs/utils/build-logql.d.ts +2 -0
  33. package/dist/components/features/logs/utils/constants.d.ts +6 -0
  34. package/dist/components/features/logs/utils/facets.d.ts +3 -0
  35. package/dist/components/features/logs/utils/flatten-loki.d.ts +3 -0
  36. package/dist/components/features/logs/utils/format-timestamp.d.ts +4 -0
  37. package/dist/components/features/logs/utils/index.d.ts +8 -0
  38. package/dist/components/features/logs/utils/parse-log-line.d.ts +7 -0
  39. package/dist/components/features/logs/utils/severity.d.ts +5 -0
  40. package/dist/components/features/logs/utils/time-range.d.ts +3 -0
  41. package/dist/data-table/index.mjs +1 -1
  42. package/dist/{data-table-DjGvAdO_.mjs → data-table-Du7_FLBi.mjs} +4 -4
  43. package/dist/date-picker/index.mjs +3 -3
  44. package/dist/date-time/index.mjs +2 -0
  45. package/dist/date-time-CA-09lnw.mjs +134 -0
  46. package/dist/date-time-picker/index.mjs +1 -1
  47. package/dist/{date-time-picker-D3-DeNo8.mjs → date-time-picker-BzcHrf-A.mjs} +1 -1
  48. package/dist/{picker-b5-x-EHs.mjs → date-time-range-picker-Dog33Ry0.mjs} +22 -456
  49. package/dist/form/index.mjs +1 -1
  50. package/dist/{form-BUUNQfC9.mjs → form-BhBsoMFA.mjs} +4 -4
  51. package/dist/grouped-table/index.mjs +1 -1
  52. package/dist/{grouped-table-D4J3ioHb.mjs → grouped-table--jejU1Jy.mjs} +1 -1
  53. package/dist/index.mjs +21 -20
  54. package/dist/logs/fixtures/index.mjs +231 -0
  55. package/dist/logs/index.mjs +980 -0
  56. package/dist/mobile-sheet/index.mjs +1 -1
  57. package/dist/{mobile-sheet-D9L-JOee.mjs → mobile-sheet-BYp-QQmn.mjs} +1 -1
  58. package/dist/more-actions/index.mjs +1 -1
  59. package/dist/{more-actions-Bx443Ccx.mjs → more-actions-B1FUf2rm.mjs} +1 -1
  60. package/dist/multi-select/index.mjs +1 -1
  61. package/dist/{multi-select-PHaMEbY-.mjs → multi-select-PtdMChPF.mjs} +1 -1
  62. package/dist/picker/index.mjs +2 -1
  63. package/dist/picker-raVuDxsh.mjs +453 -0
  64. package/dist/responsive-dropdown/index.mjs +1 -1
  65. package/dist/{responsive-dropdown-fCmduKaz.mjs → responsive-dropdown-n0Txkax5.mjs} +1 -1
  66. package/dist/responsive-popover/index.mjs +1 -1
  67. package/dist/{responsive-popover-DJZgXWIE.mjs → responsive-popover-C_1nzIvm.mjs} +1 -1
  68. package/dist/rich-text-editor/index.mjs +1 -1
  69. package/dist/{rich-text-editor-CGuSn8xi.mjs → rich-text-editor-Dohp-pbf.mjs} +1 -1
  70. package/dist/sheet/index.mjs +1 -1
  71. package/dist/{sheet-BZViad2U.mjs → sheet-C2i2k5i2.mjs} +2 -2
  72. package/dist/sidebar/index.mjs +1 -1
  73. package/dist/{sidebar-BRUNOqXi.mjs → sidebar-D6BoAr6C.mjs} +53 -16
  74. package/dist/styles/shadcn/animations.css +2 -2
  75. package/dist/task-queue/index.mjs +1 -1
  76. package/dist/{task-queue-dropdown-pqFd4rSf.mjs → task-queue-dropdown-ClLLTMxU.mjs} +1 -1
  77. package/dist/time-picker/index.mjs +1 -1
  78. package/dist/{time-picker-B_fN5vhO.mjs → time-picker-CFhaHTtI.mjs} +1 -1
  79. package/dist/{to-api-format-43Boq7OR.mjs → to-api-format-WC7b9Mcv.mjs} +2 -2
  80. package/package.json +16 -1
@@ -0,0 +1,980 @@
1
+ import { t as cn } from "../utils-Bu32wN-x.mjs";
2
+ import { t as Badge } from "../badge-BTNW_FEU.mjs";
3
+ import { t as Button } from "../button-A1qTjZ8U.mjs";
4
+ import { t as Checkbox } from "../checkbox-C6P12fio.mjs";
5
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../collapsible/index.mjs";
6
+ import { t as Input } from "../input-j4tbmYYz.mjs";
7
+ import { t as Sheet } from "../sheet-C2i2k5i2.mjs";
8
+ import { t as useBreakpoint } from "../use-breakpoint-CwiUJSmk.mjs";
9
+ import { Skeleton } from "../skeleton/index.mjs";
10
+ import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "../table/index.mjs";
11
+ import { t as DateTimeRangePicker } from "../date-time-range-picker-Dog33Ry0.mjs";
12
+ import { t as DateTime } from "../date-time-CA-09lnw.mjs";
13
+ import { t as useCopyToClipboard } from "../use-copy-to-clipboard-DZDq_85T.mjs";
14
+ import { ChevronDown, ChevronUp, Copy, Download, RefreshCw, Search, SlidersHorizontal, X } from "lucide-react";
15
+ import { createContext, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
16
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
+ import { AnimatePresence, motion, useReducedMotion } from "motion/react";
18
+ import { format } from "date-fns";
19
+ //#region src/components/features/logs/hooks/use-logs.ts
20
+ const LogsContext = createContext(null);
21
+ function useLogs() {
22
+ const context = use(LogsContext);
23
+ if (!context) throw new Error("Logs components must be used within <Logs.Root>");
24
+ return context;
25
+ }
26
+ //#endregion
27
+ //#region src/components/features/logs/utils/parse-log-line.ts
28
+ const ACCESS_LOG_RE = /^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\S+)\s+(\d{3})\s+(\d+)ms\b/i;
29
+ const PATH_TOKEN_RE = /(?:^|\s)(\/\S+)/;
30
+ function parseLogLine(line) {
31
+ const match = ACCESS_LOG_RE.exec(line);
32
+ if (!match) return {
33
+ kind: "text",
34
+ line
35
+ };
36
+ const method = match[1];
37
+ const path = match[2];
38
+ const status = match[3];
39
+ const duration = match[4];
40
+ if (!method || !path || !status || !duration) return {
41
+ kind: "text",
42
+ line
43
+ };
44
+ return {
45
+ kind: "http",
46
+ method: method.toUpperCase(),
47
+ path,
48
+ status: Number(status),
49
+ durationMs: Number(duration)
50
+ };
51
+ }
52
+ function logLineDisplay(line) {
53
+ const parsed = parseLogLine(line);
54
+ if (parsed.kind === "http") {
55
+ const match = ACCESS_LOG_RE.exec(line);
56
+ const rest = match ? line.slice(match[0].length).trim() : "";
57
+ return {
58
+ parsed,
59
+ path: parsed.path,
60
+ message: extraMessage(rest)
61
+ };
62
+ }
63
+ const path = PATH_TOKEN_RE.exec(line)?.[1] ?? null;
64
+ if (!path) return {
65
+ parsed,
66
+ path: null,
67
+ message: line
68
+ };
69
+ return {
70
+ parsed,
71
+ path,
72
+ message: extraMessage(stripOnce(line, path))
73
+ };
74
+ }
75
+ /** Drop leftover that is only fields already shown (method, status, path, duration, label=value). */
76
+ function extraMessage(rest) {
77
+ if (!rest) return "";
78
+ return rest.split(/\s+/).filter(Boolean).some((token) => !/^[A-Z_]\w*=\S+$/i.test(token)) ? rest : "";
79
+ }
80
+ function stripOnce(line, token) {
81
+ const index = line.indexOf(token);
82
+ if (index === -1) return line;
83
+ return `${line.slice(0, index)}${line.slice(index + token.length)}`.replace(/\s+/g, " ").trim();
84
+ }
85
+ //#endregion
86
+ //#region src/components/features/logs/utils/severity.ts
87
+ const SEVERITY_BADGE = {
88
+ ERROR: "danger",
89
+ FATAL: "danger",
90
+ CRITICAL: "danger",
91
+ WARN: "warning",
92
+ WARNING: "warning",
93
+ INFO: "info",
94
+ DEBUG: "muted",
95
+ TRACE: "muted"
96
+ };
97
+ function severityBadgeType(severity) {
98
+ if (!severity) return "muted";
99
+ return SEVERITY_BADGE[severity.toUpperCase()] ?? "muted";
100
+ }
101
+ function httpStatusBadgeType(status) {
102
+ if (status >= 500) return "danger";
103
+ if (status >= 400) return "warning";
104
+ if (status >= 300) return "info";
105
+ if (status >= 200) return "success";
106
+ return "muted";
107
+ }
108
+ function httpStatusTextClass(status) {
109
+ switch (httpStatusBadgeType(status)) {
110
+ case "danger": return "text-[var(--color-badge-danger)]";
111
+ case "warning": return "text-[var(--color-badge-warning)]";
112
+ case "info": return "text-[var(--color-badge-info)]";
113
+ case "success": return "text-[var(--color-badge-success)]";
114
+ default: return "text-muted-foreground";
115
+ }
116
+ }
117
+ //#endregion
118
+ //#region src/components/features/logs/components/status-badge.tsx
119
+ function LogsSeverityBadge({ severity }) {
120
+ return /* @__PURE__ */ jsx(Badge, {
121
+ type: severityBadgeType(severity),
122
+ theme: "light",
123
+ className: "h-4 px-1.5 py-0 font-mono text-[10px] leading-4 font-medium",
124
+ children: severity ?? "LOG"
125
+ });
126
+ }
127
+ function LogsStatusBadge({ parsed }) {
128
+ if (parsed.kind !== "http") return /* @__PURE__ */ jsx("span", {
129
+ className: "text-muted-foreground",
130
+ children: "—"
131
+ });
132
+ return /* @__PURE__ */ jsxs("span", {
133
+ className: "inline-grid grid-cols-[7ch_auto] gap-x-2 font-mono text-[11px] font-medium",
134
+ children: [/* @__PURE__ */ jsx("span", {
135
+ className: "text-muted-foreground",
136
+ children: parsed.method
137
+ }), /* @__PURE__ */ jsx("span", {
138
+ className: cn("tabular-nums", httpStatusTextClass(parsed.status)),
139
+ children: parsed.status
140
+ })]
141
+ });
142
+ }
143
+ //#endregion
144
+ //#region src/components/features/logs/components/detail.tsx
145
+ function MetaCell({ label, children, className }) {
146
+ return /* @__PURE__ */ jsxs("div", {
147
+ className: cn("bg-background min-w-0 px-3 py-2.5", className),
148
+ children: [/* @__PURE__ */ jsx("div", {
149
+ className: "text-muted-foreground mb-1 text-[11px] font-medium",
150
+ children: label
151
+ }), /* @__PURE__ */ jsx("div", {
152
+ className: "font-mono text-xs break-all",
153
+ children
154
+ })]
155
+ });
156
+ }
157
+ function DetailBody({ showClose = true }) {
158
+ const { selectedEntry, selectPrevious, selectNext, setSelectedId } = useLogs();
159
+ const [, copy] = useCopyToClipboard();
160
+ if (!selectedEntry) return null;
161
+ const parsed = parseLogLine(selectedEntry.line);
162
+ const title = parsed.kind === "http" ? `${parsed.method} ${parsed.path}` : selectedEntry.labels.service_name ?? "Log";
163
+ const service = selectedEntry.labels.service_name;
164
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("header", {
165
+ className: "flex items-start gap-2 border-b px-4 py-3",
166
+ children: [/* @__PURE__ */ jsxs("div", {
167
+ className: "min-w-0 flex-1",
168
+ children: [
169
+ /* @__PURE__ */ jsx("div", {
170
+ className: "mb-1.5",
171
+ children: /* @__PURE__ */ jsx(LogsSeverityBadge, { severity: selectedEntry.labels.severity })
172
+ }),
173
+ /* @__PURE__ */ jsx("h3", {
174
+ className: "truncate font-mono text-sm font-medium",
175
+ children: title
176
+ }),
177
+ /* @__PURE__ */ jsxs("div", {
178
+ className: "text-muted-foreground mt-1 flex min-w-0 items-center gap-1.5 text-xs",
179
+ children: [service && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
180
+ className: "truncate font-mono",
181
+ children: service
182
+ }), /* @__PURE__ */ jsx("span", {
183
+ "aria-hidden": "true",
184
+ children: "·"
185
+ })] }), /* @__PURE__ */ jsx(DateTime, {
186
+ date: selectedEntry.timestamp,
187
+ timestamp: selectedEntry.timestampNs,
188
+ variant: "relative",
189
+ tooltip: "detailed",
190
+ className: "text-muted-foreground text-xs"
191
+ })]
192
+ })
193
+ ]
194
+ }), /* @__PURE__ */ jsxs("div", {
195
+ className: "flex shrink-0 items-center gap-1",
196
+ children: [
197
+ /* @__PURE__ */ jsx(Button, {
198
+ type: "secondary",
199
+ theme: "borderless",
200
+ size: "icon",
201
+ "aria-label": "Previous log",
202
+ onClick: selectPrevious,
203
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-4" })
204
+ }),
205
+ /* @__PURE__ */ jsx(Button, {
206
+ type: "secondary",
207
+ theme: "borderless",
208
+ size: "icon",
209
+ "aria-label": "Next log",
210
+ onClick: selectNext,
211
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" })
212
+ }),
213
+ /* @__PURE__ */ jsx(Button, {
214
+ type: "secondary",
215
+ theme: "borderless",
216
+ size: "icon",
217
+ "aria-label": "Copy log line",
218
+ onClick: () => copy(selectedEntry.line, { withToast: true }),
219
+ children: /* @__PURE__ */ jsx(Copy, { className: "size-4" })
220
+ }),
221
+ showClose && /* @__PURE__ */ jsx(Button, {
222
+ type: "secondary",
223
+ theme: "borderless",
224
+ size: "icon",
225
+ "aria-label": "Close details",
226
+ onClick: () => setSelectedId(null),
227
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
228
+ })
229
+ ]
230
+ })]
231
+ }), /* @__PURE__ */ jsxs("div", {
232
+ className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto px-4 py-4",
233
+ children: [
234
+ /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
235
+ className: "text-muted-foreground mb-2 text-xs font-medium",
236
+ children: "Message"
237
+ }), /* @__PURE__ */ jsx("pre", {
238
+ className: "bg-muted text-foreground overflow-x-auto rounded-md border p-3 font-mono text-xs leading-5 whitespace-pre-wrap break-all",
239
+ children: selectedEntry.line
240
+ })] }),
241
+ parsed.kind === "http" && /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
242
+ className: "text-muted-foreground mb-2 text-xs font-medium",
243
+ children: "Request"
244
+ }), /* @__PURE__ */ jsxs("div", {
245
+ className: "bg-border grid grid-cols-3 gap-px overflow-hidden rounded-md border",
246
+ children: [
247
+ /* @__PURE__ */ jsx(MetaCell, {
248
+ label: "Method",
249
+ children: parsed.method
250
+ }),
251
+ /* @__PURE__ */ jsx(MetaCell, {
252
+ label: "Status",
253
+ children: /* @__PURE__ */ jsx("span", {
254
+ className: cn("font-medium tabular-nums", httpStatusTextClass(parsed.status)),
255
+ children: parsed.status
256
+ })
257
+ }),
258
+ /* @__PURE__ */ jsxs(MetaCell, {
259
+ label: "Duration",
260
+ children: [parsed.durationMs, "ms"]
261
+ }),
262
+ /* @__PURE__ */ jsx(MetaCell, {
263
+ label: "Path",
264
+ className: "col-span-3",
265
+ children: parsed.path
266
+ })
267
+ ]
268
+ })] }),
269
+ /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
270
+ className: "text-muted-foreground mb-2 text-xs font-medium",
271
+ children: "Labels"
272
+ }), /* @__PURE__ */ jsx("dl", {
273
+ className: "overflow-hidden rounded-md border",
274
+ children: Object.entries(selectedEntry.labels).map(([key, value]) => /* @__PURE__ */ jsxs("div", {
275
+ className: "grid grid-cols-[minmax(0,8.5rem)_minmax(0,1fr)] gap-x-3 border-b px-3 py-2 last:border-b-0",
276
+ children: [/* @__PURE__ */ jsx("dt", {
277
+ className: "text-muted-foreground truncate font-mono text-[11px]",
278
+ children: key
279
+ }), /* @__PURE__ */ jsx("dd", {
280
+ className: "min-w-0 font-mono text-xs break-all",
281
+ children: value
282
+ })]
283
+ }, key))
284
+ })] })
285
+ ]
286
+ })] });
287
+ }
288
+ function LogsDetail({ className }) {
289
+ const { selectedEntry, setSelectedId } = useLogs();
290
+ const breakpoint = useBreakpoint();
291
+ const reduceMotion = useReducedMotion();
292
+ const isDesktop = breakpoint === "desktop";
293
+ const open = selectedEntry !== null;
294
+ if (!isDesktop) return /* @__PURE__ */ jsx(Sheet, {
295
+ open,
296
+ onOpenChange: (next) => {
297
+ if (!next) setSelectedId(null);
298
+ },
299
+ children: /* @__PURE__ */ jsxs(Sheet.Content, {
300
+ side: "right",
301
+ showCloseButton: false,
302
+ className: "sm:max-w-md w-full gap-0 p-0",
303
+ "data-slot": "logs-detail",
304
+ children: [
305
+ /* @__PURE__ */ jsx(Sheet.Title, {
306
+ className: "sr-only",
307
+ children: "Log details"
308
+ }),
309
+ /* @__PURE__ */ jsx(Sheet.Description, {
310
+ className: "sr-only",
311
+ children: "Selected log entry"
312
+ }),
313
+ /* @__PURE__ */ jsx("div", {
314
+ className: "flex h-full flex-col",
315
+ children: /* @__PURE__ */ jsx(DetailBody, {})
316
+ })
317
+ ]
318
+ })
319
+ });
320
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(motion.aside, {
321
+ "data-slot": "logs-detail",
322
+ initial: reduceMotion ? false : { width: 0 },
323
+ animate: { width: 400 },
324
+ exit: { width: 0 },
325
+ transition: reduceMotion ? { duration: 0 } : {
326
+ duration: .2,
327
+ ease: "easeOut"
328
+ },
329
+ className: cn("bg-background flex h-full shrink-0 flex-col overflow-hidden border-l", className),
330
+ children: /* @__PURE__ */ jsx("div", {
331
+ className: "flex h-full w-[400px] flex-col",
332
+ children: /* @__PURE__ */ jsx(DetailBody, {})
333
+ })
334
+ }) });
335
+ }
336
+ //#endregion
337
+ //#region src/components/features/logs/components/filters.tsx
338
+ function LogsFilterGroup({ facet, defaultOpen }) {
339
+ const { filters, toggleFilterValue } = useLogs();
340
+ const selected = filters[facet.name] ?? [];
341
+ return /* @__PURE__ */ jsxs(Collapsible, {
342
+ defaultOpen,
343
+ className: "border-b border-border",
344
+ children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
345
+ className: "hover:bg-muted/40 flex w-full items-center justify-between px-3 py-2 text-left text-sm font-medium",
346
+ children: [facet.label, /* @__PURE__ */ jsx(ChevronDown, { className: "text-muted-foreground size-4 transition-transform [[data-state=open]_&]:rotate-180" })]
347
+ }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("ul", {
348
+ className: "flex flex-col gap-1 px-3 pb-3",
349
+ children: facet.options.map((option) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
350
+ className: "hover:bg-muted/40 flex cursor-pointer items-center gap-2 rounded-md px-1 py-1 text-sm",
351
+ children: [
352
+ /* @__PURE__ */ jsx(Checkbox, {
353
+ checked: selected.includes(option.value),
354
+ onCheckedChange: () => toggleFilterValue(facet.name, option.value),
355
+ "aria-label": option.value
356
+ }),
357
+ /* @__PURE__ */ jsx("span", {
358
+ className: "min-w-0 flex-1 truncate font-mono text-xs",
359
+ children: option.value
360
+ }),
361
+ option.count !== void 0 && /* @__PURE__ */ jsx("span", {
362
+ className: "text-muted-foreground tabular-nums text-xs",
363
+ children: option.count
364
+ })
365
+ ]
366
+ }) }, option.value))
367
+ }) })]
368
+ });
369
+ }
370
+ function LogsTimeRangeFilter({ className }) {
371
+ const { timeRange, setTimeRange } = useLogs();
372
+ return /* @__PURE__ */ jsxs("div", {
373
+ className: cn("border-b border-border px-3 py-3", className),
374
+ children: [/* @__PURE__ */ jsx("div", {
375
+ className: "text-muted-foreground mb-2 text-xs font-medium",
376
+ children: "Time range"
377
+ }), /* @__PURE__ */ jsx(DateTimeRangePicker, {
378
+ value: timeRange,
379
+ onChange: (next) => {
380
+ if (next) setTimeRange({
381
+ from: next.from,
382
+ to: next.to
383
+ });
384
+ },
385
+ placeholder: "Select range",
386
+ disableFuture: true,
387
+ hideTimezone: true,
388
+ numberOfMonths: 1,
389
+ className: "w-full",
390
+ triggerClassName: "w-full justify-start text-xs",
391
+ sheetTitle: "Select time range"
392
+ })]
393
+ });
394
+ }
395
+ function LogsFilters({ className }) {
396
+ const { facets, hasActiveFilters, resetFilters } = useLogs();
397
+ return /* @__PURE__ */ jsxs("aside", {
398
+ "data-slot": "logs-filters",
399
+ className: cn("bg-background flex h-full min-h-0 w-64 shrink-0 flex-col border-r", className),
400
+ children: [/* @__PURE__ */ jsxs("div", {
401
+ className: "flex items-center justify-between px-3 py-3",
402
+ children: [/* @__PURE__ */ jsx("h2", {
403
+ className: "text-sm font-semibold",
404
+ children: "Filters"
405
+ }), /* @__PURE__ */ jsx("button", {
406
+ type: "button",
407
+ className: "text-muted-foreground hover:text-foreground text-xs disabled:opacity-40",
408
+ disabled: !hasActiveFilters,
409
+ onClick: resetFilters,
410
+ children: "Reset"
411
+ })]
412
+ }), /* @__PURE__ */ jsxs("div", {
413
+ className: "min-h-0 flex-1 overflow-y-auto",
414
+ children: [/* @__PURE__ */ jsx(LogsTimeRangeFilter, {}), facets.map((facet, index) => /* @__PURE__ */ jsx(LogsFilterGroup, {
415
+ facet,
416
+ defaultOpen: index === 0
417
+ }, facet.name))]
418
+ })]
419
+ });
420
+ }
421
+ //#endregion
422
+ //#region src/components/features/logs/utils/format-timestamp.ts
423
+ function formatLogTimestamp(date) {
424
+ return format(date, "MMM dd HH:mm:ss.SS").toUpperCase();
425
+ }
426
+ //#endregion
427
+ //#region src/components/features/logs/components/table.tsx
428
+ const COLUMN_LABEL = {
429
+ time: "Time",
430
+ severity: "Severity",
431
+ status: "Status",
432
+ service: "Service",
433
+ resource: "Resource",
434
+ path: "Path",
435
+ message: "Message"
436
+ };
437
+ const COLUMN_HEAD_CLASS = {
438
+ time: "w-[168px]",
439
+ severity: "w-[88px]",
440
+ status: "w-[110px]",
441
+ service: "w-[160px]",
442
+ resource: "w-[160px]",
443
+ path: "w-[200px]",
444
+ message: ""
445
+ };
446
+ function LogCell({ column, entry, parsed, path, message }) {
447
+ switch (column) {
448
+ case "time": return /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("time", {
449
+ dateTime: entry.timestamp.toISOString(),
450
+ className: "text-muted-foreground font-mono text-xs whitespace-nowrap",
451
+ children: formatLogTimestamp(entry.timestamp)
452
+ }) });
453
+ case "severity": return /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(LogsSeverityBadge, { severity: entry.labels.severity }) });
454
+ case "status": return /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(LogsStatusBadge, { parsed }) });
455
+ case "service": return /* @__PURE__ */ jsx(TableCell, {
456
+ className: "text-muted-foreground truncate font-mono text-xs",
457
+ children: entry.labels.service_name ?? "—"
458
+ });
459
+ case "resource": return /* @__PURE__ */ jsx(TableCell, {
460
+ className: "text-muted-foreground truncate font-mono text-xs",
461
+ children: entry.labels.resource_name ?? "—"
462
+ });
463
+ case "path": return /* @__PURE__ */ jsx(TableCell, {
464
+ className: cn("truncate font-mono text-xs", !path && "text-muted-foreground"),
465
+ title: path ?? void 0,
466
+ children: path ?? "—"
467
+ });
468
+ case "message": return /* @__PURE__ */ jsx(TableCell, {
469
+ className: "truncate font-mono text-xs",
470
+ children: message
471
+ });
472
+ }
473
+ }
474
+ function LogsTable({ className }) {
475
+ const { entries, columns, selectedId, setSelectedId, isLoading, error, selectPrevious, selectNext } = useLogs();
476
+ const scrollerRef = useRef(null);
477
+ function onKeyDown(event) {
478
+ if (event.key === "ArrowDown" || event.key === "j") {
479
+ event.preventDefault();
480
+ selectNext();
481
+ }
482
+ if (event.key === "ArrowUp" || event.key === "k") {
483
+ event.preventDefault();
484
+ selectPrevious();
485
+ }
486
+ if (event.key === "Escape") setSelectedId(null);
487
+ }
488
+ return /* @__PURE__ */ jsxs("div", {
489
+ ref: scrollerRef,
490
+ "data-slot": "logs-table",
491
+ className: cn("min-h-0 flex-1 overflow-auto outline-none", className),
492
+ tabIndex: 0,
493
+ "aria-busy": isLoading || void 0,
494
+ onKeyDown,
495
+ children: [error && /* @__PURE__ */ jsx("div", {
496
+ role: "alert",
497
+ className: "text-destructive px-4 py-3 text-sm",
498
+ children: error
499
+ }), /* @__PURE__ */ jsxs("table", {
500
+ className: "w-full caption-bottom table-fixed text-sm",
501
+ children: [/* @__PURE__ */ jsx(TableHeader, {
502
+ className: "bg-background sticky top-0 z-10",
503
+ children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((column) => /* @__PURE__ */ jsx(TableHead, {
504
+ className: COLUMN_HEAD_CLASS[column],
505
+ children: COLUMN_LABEL[column]
506
+ }, column)) })
507
+ }), /* @__PURE__ */ jsxs(TableBody, { children: [
508
+ isLoading && entries.length === 0 && Array.from({ length: 8 }, (_, index) => /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
509
+ colSpan: columns.length,
510
+ children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" })
511
+ }) }, `skeleton-${index}`)),
512
+ !isLoading && entries.length === 0 && !error && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
513
+ colSpan: columns.length,
514
+ className: "text-muted-foreground py-10 text-center text-sm",
515
+ children: "No logs in this time range"
516
+ }) }),
517
+ entries.map((entry) => {
518
+ const { parsed, path, message } = logLineDisplay(entry.line);
519
+ const selected = entry.id === selectedId;
520
+ return /* @__PURE__ */ jsx(TableRow, {
521
+ "data-state": selected ? "selected" : void 0,
522
+ "aria-selected": selected,
523
+ className: cn("cursor-pointer hover:bg-foreground/[0.08]", "data-[state=selected]:bg-foreground/[0.14]", "data-[state=selected]:hover:bg-foreground/[0.14]"),
524
+ onClick: () => {
525
+ setSelectedId(selected ? null : entry.id);
526
+ scrollerRef.current?.focus();
527
+ },
528
+ children: columns.map((column) => /* @__PURE__ */ jsx(LogCell, {
529
+ column,
530
+ entry,
531
+ parsed,
532
+ path,
533
+ message
534
+ }, column))
535
+ }, entry.id);
536
+ })
537
+ ] })]
538
+ })]
539
+ });
540
+ }
541
+ //#endregion
542
+ //#region src/components/features/logs/components/toolbar.tsx
543
+ function LogsSearch({ placeholder = "Search logs...", className }) {
544
+ const { search, setSearch } = useLogs();
545
+ const [value, setValue] = useState(search);
546
+ const searchRef = useRef(search);
547
+ const skipNextSync = useRef(false);
548
+ searchRef.current = search;
549
+ useEffect(() => {
550
+ if (skipNextSync.current) {
551
+ skipNextSync.current = false;
552
+ return;
553
+ }
554
+ setValue(search);
555
+ }, [search]);
556
+ useEffect(() => {
557
+ if (value === searchRef.current) return;
558
+ const timer = setTimeout(() => {
559
+ skipNextSync.current = true;
560
+ setSearch(value);
561
+ }, 250);
562
+ return () => clearTimeout(timer);
563
+ }, [value, setSearch]);
564
+ return /* @__PURE__ */ jsxs("div", {
565
+ className: cn("relative min-w-0 flex-1", className),
566
+ children: [/* @__PURE__ */ jsx(Search, { className: "text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2" }), /* @__PURE__ */ jsx(Input, {
567
+ value,
568
+ onChange: (event) => setValue(event.target.value),
569
+ placeholder,
570
+ "aria-label": placeholder,
571
+ className: "pl-9",
572
+ "data-slot": "logs-search"
573
+ })]
574
+ });
575
+ }
576
+ function LogsLiveToggle({ className }) {
577
+ const { live, setLive } = useLogs();
578
+ return /* @__PURE__ */ jsxs(Button, {
579
+ type: "secondary",
580
+ theme: "outline",
581
+ size: "small",
582
+ "aria-pressed": live,
583
+ onClick: () => setLive(!live),
584
+ className: cn("gap-2", className),
585
+ "data-slot": "logs-live",
586
+ children: [/* @__PURE__ */ jsx("span", { className: cn("size-2 rounded-full", live ? "bg-destructive animate-pulse" : "bg-muted-foreground/40") }), "Live"]
587
+ });
588
+ }
589
+ function LogsToolbar({ children, className }) {
590
+ const { onRefresh, onExport, entries } = useLogs();
591
+ return /* @__PURE__ */ jsxs("div", {
592
+ "data-slot": "logs-toolbar",
593
+ className: cn("flex items-center gap-2 border-b px-3 py-2", className),
594
+ children: [
595
+ /* @__PURE__ */ jsx(LogsSearch, {}),
596
+ children,
597
+ /* @__PURE__ */ jsx(LogsLiveToggle, {}),
598
+ onRefresh && /* @__PURE__ */ jsx(Button, {
599
+ type: "secondary",
600
+ theme: "outline",
601
+ size: "icon",
602
+ "aria-label": "Refresh logs",
603
+ onClick: onRefresh,
604
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: "size-4" })
605
+ }),
606
+ onExport && /* @__PURE__ */ jsx(Button, {
607
+ type: "secondary",
608
+ theme: "outline",
609
+ size: "icon",
610
+ "aria-label": "Export logs",
611
+ onClick: () => onExport(entries),
612
+ children: /* @__PURE__ */ jsx(Download, { className: "size-4" })
613
+ })
614
+ ]
615
+ });
616
+ }
617
+ //#endregion
618
+ //#region src/components/features/logs/components/explorer.tsx
619
+ function LogsExplorer({ className }) {
620
+ const isDesktop = useBreakpoint() === "desktop";
621
+ const [filtersOpen, setFiltersOpen] = useState(false);
622
+ return /* @__PURE__ */ jsxs("div", {
623
+ "data-slot": "logs-explorer",
624
+ className: cn("flex min-h-0 flex-1 overflow-hidden", className),
625
+ children: [
626
+ isDesktop ? /* @__PURE__ */ jsx(LogsFilters, {}) : /* @__PURE__ */ jsx(Sheet, {
627
+ open: filtersOpen,
628
+ onOpenChange: setFiltersOpen,
629
+ children: /* @__PURE__ */ jsxs(Sheet.Content, {
630
+ side: "left",
631
+ className: "w-72 p-0 sm:max-w-72",
632
+ children: [
633
+ /* @__PURE__ */ jsx(Sheet.Title, {
634
+ className: "sr-only",
635
+ children: "Filters"
636
+ }),
637
+ /* @__PURE__ */ jsx(Sheet.Description, {
638
+ className: "sr-only",
639
+ children: "Log filters"
640
+ }),
641
+ /* @__PURE__ */ jsx(LogsFilters, { className: "w-full border-r-0 [&>div:first-child]:pr-12" })
642
+ ]
643
+ })
644
+ }),
645
+ /* @__PURE__ */ jsxs("div", {
646
+ className: "flex min-w-0 flex-1 flex-col",
647
+ children: [/* @__PURE__ */ jsx(LogsToolbar, { children: !isDesktop && /* @__PURE__ */ jsxs(Button, {
648
+ type: "secondary",
649
+ theme: "outline",
650
+ size: "small",
651
+ onClick: () => setFiltersOpen(true),
652
+ children: [/* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-4" }), "Filters"]
653
+ }) }), /* @__PURE__ */ jsxs("div", {
654
+ className: "flex min-h-0 flex-1",
655
+ children: [/* @__PURE__ */ jsx(LogsTable, {}), isDesktop && /* @__PURE__ */ jsx(LogsDetail, {})]
656
+ })]
657
+ }),
658
+ !isDesktop && /* @__PURE__ */ jsx(LogsDetail, {})
659
+ ]
660
+ });
661
+ }
662
+ //#endregion
663
+ //#region src/components/features/logs/hooks/use-controllable-state.ts
664
+ function useControllableState({ value, defaultValue, onChange }) {
665
+ const [internal, setInternal] = useState(defaultValue);
666
+ const isControlled = value !== void 0;
667
+ return [isControlled ? value : internal, useCallback((next) => {
668
+ if (!isControlled) setInternal(next);
669
+ onChange?.(next);
670
+ }, [isControlled, onChange])];
671
+ }
672
+ //#endregion
673
+ //#region src/components/features/logs/utils/constants.ts
674
+ const DEFAULT_LOG_COLUMNS = [
675
+ "time",
676
+ "severity",
677
+ "status",
678
+ "service",
679
+ "path",
680
+ "message"
681
+ ];
682
+ const CANONICAL_FACET_NAMES = [
683
+ "severity",
684
+ "service_name",
685
+ "resource_name"
686
+ ];
687
+ const FACET_LABELS = {
688
+ severity: "Severity",
689
+ service_name: "Service",
690
+ resource_name: "Resource"
691
+ };
692
+ const SEVERITY_ORDER = [
693
+ "ERROR",
694
+ "FATAL",
695
+ "WARN",
696
+ "WARNING",
697
+ "INFO",
698
+ "DEBUG",
699
+ "TRACE"
700
+ ];
701
+ const DEFAULT_LOGQL_MATCHER = "{service_name=~\".+\"}";
702
+ //#endregion
703
+ //#region src/components/features/logs/utils/facets.ts
704
+ function facetLabel(name) {
705
+ return FACET_LABELS[name] ?? name.replaceAll("_", " ");
706
+ }
707
+ function compareFacetValues(name, a, b) {
708
+ if (name === "severity") {
709
+ const aIndex = SEVERITY_ORDER.indexOf(a);
710
+ const bIndex = SEVERITY_ORDER.indexOf(b);
711
+ const aRank = aIndex === -1 ? SEVERITY_ORDER.length : aIndex;
712
+ const bRank = bIndex === -1 ? SEVERITY_ORDER.length : bIndex;
713
+ if (aRank !== bRank) return aRank - bRank;
714
+ }
715
+ return a.localeCompare(b);
716
+ }
717
+ function compareFacetNames(a, b) {
718
+ const aIndex = CANONICAL_FACET_NAMES.indexOf(a);
719
+ const bIndex = CANONICAL_FACET_NAMES.indexOf(b);
720
+ const aRank = aIndex === -1 ? CANONICAL_FACET_NAMES.length : aIndex;
721
+ const bRank = bIndex === -1 ? CANONICAL_FACET_NAMES.length : bIndex;
722
+ if (aRank !== bRank) return aRank - bRank;
723
+ return a.localeCompare(b);
724
+ }
725
+ function facetsFromEntries(entries, names = CANONICAL_FACET_NAMES) {
726
+ const allow = new Set(names);
727
+ const counts = /* @__PURE__ */ new Map();
728
+ for (const entry of entries) for (const [name, value] of Object.entries(entry.labels)) {
729
+ if (!value || !allow.has(name)) continue;
730
+ let values = counts.get(name);
731
+ if (!values) {
732
+ values = /* @__PURE__ */ new Map();
733
+ counts.set(name, values);
734
+ }
735
+ values.set(value, (values.get(value) ?? 0) + 1);
736
+ }
737
+ return [...counts.keys()].sort(compareFacetNames).map((name) => {
738
+ const values = counts.get(name) ?? /* @__PURE__ */ new Map();
739
+ return {
740
+ name,
741
+ label: facetLabel(name),
742
+ options: [...values.entries()].sort(([a], [b]) => compareFacetValues(name, a, b)).map(([value, count]) => ({
743
+ value,
744
+ count
745
+ }))
746
+ };
747
+ });
748
+ }
749
+ function filterEntries(entries, filters, search = "") {
750
+ const query = search.trim().toLowerCase();
751
+ const active = Object.entries(filters).filter(([, values]) => values.length > 0);
752
+ return entries.filter((entry) => {
753
+ for (const [name, values] of active) {
754
+ const label = entry.labels[name];
755
+ if (!label || !values.includes(label)) return false;
756
+ }
757
+ if (query && !entry.line.toLowerCase().includes(query)) return false;
758
+ return true;
759
+ });
760
+ }
761
+ //#endregion
762
+ //#region src/components/features/logs/utils/time-range.ts
763
+ const THIRTY_MINUTES_MS = 18e5;
764
+ function lastThirtyMinutes(now = /* @__PURE__ */ new Date()) {
765
+ return {
766
+ from: (/* @__PURE__ */ new Date(now.getTime() - THIRTY_MINUTES_MS)).toISOString(),
767
+ to: now.toISOString()
768
+ };
769
+ }
770
+ function filtersAreActive(filters) {
771
+ return Object.values(filters).some((values) => values.length > 0);
772
+ }
773
+ //#endregion
774
+ //#region src/components/features/logs/components/root.tsx
775
+ function LogsRoot({ entries, facets, timeRange, defaultTimeRange, filters, defaultFilters, search, defaultSearch = "", live, defaultLive = false, selectedId, defaultSelectedId = null, isLoading = false, error, columns = DEFAULT_LOG_COLUMNS, onTimeRangeChange, onFiltersChange, onSearchChange, onLiveChange, onSelectedIdChange, onRefresh, onExport, className, children }) {
776
+ const [currentTimeRange, setTimeRange] = useControllableState({
777
+ value: timeRange,
778
+ defaultValue: defaultTimeRange ?? lastThirtyMinutes(),
779
+ onChange: onTimeRangeChange
780
+ });
781
+ const [currentFilters, setFilters] = useControllableState({
782
+ value: filters,
783
+ defaultValue: defaultFilters ?? {},
784
+ onChange: onFiltersChange
785
+ });
786
+ const [currentSearch, setSearch] = useControllableState({
787
+ value: search,
788
+ defaultValue: defaultSearch,
789
+ onChange: onSearchChange
790
+ });
791
+ const [currentLive, setLive] = useControllableState({
792
+ value: live,
793
+ defaultValue: defaultLive,
794
+ onChange: onLiveChange
795
+ });
796
+ const [currentSelectedId, setSelectedId] = useControllableState({
797
+ value: selectedId,
798
+ defaultValue: defaultSelectedId,
799
+ onChange: onSelectedIdChange
800
+ });
801
+ const resolvedFacets = useMemo(() => facets ?? facetsFromEntries(entries), [facets, entries]);
802
+ const selectedIndex = useMemo(() => entries.findIndex((entry) => entry.id === currentSelectedId), [entries, currentSelectedId]);
803
+ const selectedEntry = selectedIndex >= 0 ? entries[selectedIndex] ?? null : null;
804
+ const toggleFilterValue = useCallback((name, value) => {
805
+ const current = currentFilters[name] ?? [];
806
+ const nextValues = current.includes(value) ? current.filter((item) => item !== value) : [...current, value];
807
+ const next = { ...currentFilters };
808
+ if (nextValues.length === 0) delete next[name];
809
+ else next[name] = nextValues;
810
+ setFilters(next);
811
+ }, [currentFilters, setFilters]);
812
+ const resetFilters = useCallback(() => {
813
+ setFilters({});
814
+ }, [setFilters]);
815
+ const selectPrevious = useCallback(() => {
816
+ if (entries.length === 0) return;
817
+ if (selectedIndex <= 0) setSelectedId(entries[0].id);
818
+ else setSelectedId(entries[selectedIndex - 1].id);
819
+ }, [
820
+ entries,
821
+ selectedIndex,
822
+ setSelectedId
823
+ ]);
824
+ const selectNext = useCallback(() => {
825
+ if (entries.length === 0) return;
826
+ if (selectedIndex < 0) setSelectedId(entries[0].id);
827
+ else if (selectedIndex < entries.length - 1) setSelectedId(entries[selectedIndex + 1].id);
828
+ }, [
829
+ entries,
830
+ selectedIndex,
831
+ setSelectedId
832
+ ]);
833
+ const value = useMemo(() => ({
834
+ entries,
835
+ facets: resolvedFacets,
836
+ timeRange: currentTimeRange,
837
+ setTimeRange,
838
+ filters: currentFilters,
839
+ setFilters,
840
+ toggleFilterValue,
841
+ resetFilters,
842
+ search: currentSearch,
843
+ setSearch,
844
+ live: currentLive,
845
+ setLive,
846
+ selectedId: currentSelectedId,
847
+ setSelectedId,
848
+ selectedEntry,
849
+ selectedIndex,
850
+ selectPrevious,
851
+ selectNext,
852
+ isLoading,
853
+ error,
854
+ columns,
855
+ hasActiveFilters: filtersAreActive(currentFilters),
856
+ onRefresh,
857
+ onExport
858
+ }), [
859
+ entries,
860
+ resolvedFacets,
861
+ currentTimeRange,
862
+ setTimeRange,
863
+ currentFilters,
864
+ setFilters,
865
+ toggleFilterValue,
866
+ resetFilters,
867
+ currentSearch,
868
+ setSearch,
869
+ currentLive,
870
+ setLive,
871
+ currentSelectedId,
872
+ setSelectedId,
873
+ selectedEntry,
874
+ selectedIndex,
875
+ selectPrevious,
876
+ selectNext,
877
+ isLoading,
878
+ error,
879
+ columns,
880
+ onRefresh,
881
+ onExport
882
+ ]);
883
+ return /* @__PURE__ */ jsx(LogsContext, {
884
+ value,
885
+ children: /* @__PURE__ */ jsx("div", {
886
+ "data-slot": "logs-root",
887
+ className: cn("flex h-full min-h-0 w-full flex-col", className),
888
+ children
889
+ })
890
+ });
891
+ }
892
+ //#endregion
893
+ //#region src/components/features/logs/logs.tsx
894
+ const Logs = {
895
+ Root: LogsRoot,
896
+ Explorer: LogsExplorer,
897
+ Filters: LogsFilters,
898
+ FilterGroup: LogsFilterGroup,
899
+ TimeRangeFilter: LogsTimeRangeFilter,
900
+ Toolbar: LogsToolbar,
901
+ Search: LogsSearch,
902
+ LiveToggle: LogsLiveToggle,
903
+ Table: LogsTable,
904
+ Detail: LogsDetail
905
+ };
906
+ //#endregion
907
+ //#region src/components/features/logs/utils/build-logql.ts
908
+ /** Loki/Prometheus label names. Anything else is dropped, not interpolated. */
909
+ const LOKI_LABEL_NAME = /^[a-z_]\w*$/i;
910
+ function escapeQuoted(value) {
911
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
912
+ }
913
+ function escapeRegex(value) {
914
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
915
+ }
916
+ function matcherClause(label, values) {
917
+ if (!LOKI_LABEL_NAME.test(label)) return null;
918
+ const unique = [...new Set(values.map((v) => v.trim()).filter(Boolean))];
919
+ if (unique.length === 0) return null;
920
+ if (unique.length === 1) return `${label}="${escapeQuoted(unique[0])}"`;
921
+ return `${label}=~"${escapeQuoted(unique.map(escapeRegex).join("|"))}"`;
922
+ }
923
+ function selectorFromMatchers(matchers) {
924
+ if (!matchers) return DEFAULT_LOGQL_MATCHER;
925
+ const clauses = Object.entries(matchers).map(([label, values]) => matcherClause(label, values)).filter((clause) => clause !== null);
926
+ if (clauses.length === 0) return DEFAULT_LOGQL_MATCHER;
927
+ return `{${clauses.join(", ")}}`;
928
+ }
929
+ function buildLogQL({ matchers, lineContains } = {}) {
930
+ const selector = selectorFromMatchers(matchers);
931
+ const text = lineContains?.trim();
932
+ if (!text) return selector;
933
+ return `${selector} |= "${escapeQuoted(text)}"`;
934
+ }
935
+ //#endregion
936
+ //#region src/components/features/logs/utils/flatten-loki.ts
937
+ function nsToDate(timestampNs) {
938
+ try {
939
+ const ms = BigInt(timestampNs) / 1000000n;
940
+ return new Date(Number(ms));
941
+ } catch {
942
+ return /* @__PURE__ */ new Date(NaN);
943
+ }
944
+ }
945
+ function flattenStream(stream, streamIndex) {
946
+ const labels = stream.stream ?? {};
947
+ const values = stream.values ?? [];
948
+ const entries = [];
949
+ for (const [valueIndex, pair] of values.entries()) {
950
+ const timestampNs = pair[0];
951
+ const line = pair[1];
952
+ if (!timestampNs || line === void 0) continue;
953
+ const timestamp = nsToDate(timestampNs);
954
+ if (Number.isNaN(timestamp.getTime())) continue;
955
+ entries.push({
956
+ id: `${timestampNs}-${streamIndex}-${valueIndex}`,
957
+ timestamp,
958
+ timestampNs,
959
+ line,
960
+ labels: { ...labels }
961
+ });
962
+ }
963
+ return entries;
964
+ }
965
+ function flattenLokiStreams(response) {
966
+ if (response.status !== "success" || response.data?.resultType !== "streams" || !response.data.result) return [];
967
+ const entries = response.data.result.flatMap(flattenStream);
968
+ entries.sort((a, b) => {
969
+ try {
970
+ const delta = BigInt(b.timestampNs) - BigInt(a.timestampNs);
971
+ if (delta === 0n) return a.id < b.id ? 1 : -1;
972
+ return delta > 0n ? 1 : -1;
973
+ } catch {
974
+ return a.timestampNs < b.timestampNs ? 1 : -1;
975
+ }
976
+ });
977
+ return entries;
978
+ }
979
+ //#endregion
980
+ export { Logs, buildLogQL, facetsFromEntries, filterEntries, flattenLokiStreams, httpStatusBadgeType, httpStatusTextClass, lastThirtyMinutes, logLineDisplay, nsToDate, parseLogLine, severityBadgeType, useLogs };