@datalayer/core 1.0.1 → 1.0.3

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 (103) hide show
  1. package/README.md +1 -1
  2. package/lib/api/constants.d.ts +3 -0
  3. package/lib/api/constants.js +3 -0
  4. package/lib/api/index.d.ts +1 -0
  5. package/lib/api/index.js +1 -0
  6. package/lib/api/otel/index.d.ts +12 -0
  7. package/lib/api/otel/index.js +16 -0
  8. package/lib/api/otel/logs.d.ts +19 -0
  9. package/lib/api/otel/logs.js +43 -0
  10. package/lib/api/otel/metrics.d.ts +31 -0
  11. package/lib/api/otel/metrics.js +65 -0
  12. package/lib/api/otel/query.d.ts +16 -0
  13. package/lib/api/otel/query.js +37 -0
  14. package/lib/api/otel/services.d.ts +39 -0
  15. package/lib/api/otel/services.js +81 -0
  16. package/lib/api/otel/traces.d.ts +24 -0
  17. package/lib/api/otel/traces.js +53 -0
  18. package/lib/api/otel/types.d.ts +112 -0
  19. package/lib/api/otel/types.js +5 -0
  20. package/lib/api/spacer/index.d.ts +1 -2
  21. package/lib/api/spacer/index.js +1 -2
  22. package/lib/components/avatars/BoringAvatar.d.ts +3 -1
  23. package/lib/components/avatars/BoringAvatar.js +15 -14
  24. package/lib/components/avatars/BoringAvatar.stories.d.ts +2 -1
  25. package/lib/components/storage/ContentsBrowser.d.ts +6 -0
  26. package/lib/components/storage/ContentsBrowser.js +7 -8
  27. package/lib/config/Configuration.d.ts +4 -0
  28. package/lib/hooks/index.d.ts +2 -0
  29. package/lib/hooks/index.js +2 -0
  30. package/lib/hooks/useCache.d.ts +16 -40
  31. package/lib/hooks/useCache.js +28 -233
  32. package/lib/hooks/useProjectStore.d.ts +58 -0
  33. package/lib/hooks/useProjectStore.js +64 -0
  34. package/lib/hooks/useProjects.d.ts +590 -0
  35. package/lib/hooks/useProjects.js +166 -0
  36. package/lib/index.d.ts +2 -1
  37. package/lib/index.js +4 -2
  38. package/lib/models/Page.d.ts +2 -0
  39. package/lib/otel/OtelLive.d.ts +12 -0
  40. package/lib/otel/OtelLive.js +354 -0
  41. package/lib/otel/OtelLogsList.d.ts +11 -0
  42. package/lib/otel/OtelLogsList.js +137 -0
  43. package/lib/otel/OtelMetricsChart.d.ts +22 -0
  44. package/lib/otel/OtelMetricsChart.js +300 -0
  45. package/lib/otel/OtelMetricsList.d.ts +15 -0
  46. package/lib/otel/OtelMetricsList.js +213 -0
  47. package/lib/otel/OtelSearchBar.d.ts +11 -0
  48. package/lib/otel/OtelSearchBar.js +22 -0
  49. package/lib/otel/OtelSpanDetail.d.ts +11 -0
  50. package/lib/otel/OtelSpanDetail.js +172 -0
  51. package/lib/otel/OtelSpanTree.d.ts +11 -0
  52. package/lib/otel/OtelSpanTree.js +176 -0
  53. package/lib/otel/OtelSqlView.d.ts +16 -0
  54. package/lib/otel/OtelSqlView.js +239 -0
  55. package/lib/otel/OtelSystemView.d.ts +15 -0
  56. package/lib/otel/OtelSystemView.js +75 -0
  57. package/lib/otel/OtelTimeline.d.ts +11 -0
  58. package/lib/otel/OtelTimeline.js +101 -0
  59. package/lib/otel/OtelTimelineRangeSlider.d.ts +16 -0
  60. package/lib/otel/OtelTimelineRangeSlider.js +338 -0
  61. package/lib/otel/OtelTracesList.d.ts +13 -0
  62. package/lib/otel/OtelTracesList.js +199 -0
  63. package/lib/otel/hooks.d.ts +172 -0
  64. package/lib/otel/hooks.js +490 -0
  65. package/lib/otel/index.d.ts +25 -0
  66. package/lib/otel/index.js +19 -0
  67. package/lib/otel/types.d.ts +190 -0
  68. package/lib/otel/types.js +5 -0
  69. package/lib/otel/utils.d.ts +33 -0
  70. package/lib/otel/utils.js +181 -0
  71. package/lib/state/storage/IAMStorage.d.ts +2 -1
  72. package/lib/state/substates/CoreState.js +1 -0
  73. package/lib/utils/Jwt.d.ts +42 -0
  74. package/lib/utils/Jwt.js +44 -0
  75. package/lib/utils/index.d.ts +1 -0
  76. package/lib/utils/index.js +1 -0
  77. package/lib/views/iam/SignInSimple.d.ts +38 -0
  78. package/lib/views/iam/SignInSimple.js +80 -0
  79. package/lib/views/iam/index.d.ts +2 -0
  80. package/lib/views/iam/index.js +5 -0
  81. package/lib/views/iam-tokens/IAMTokenEdit.js +53 -4
  82. package/lib/views/iam-tokens/IAMTokens.js +65 -33
  83. package/lib/views/iam-tokens/Tokens.js +64 -32
  84. package/lib/views/index.d.ts +2 -1
  85. package/lib/views/index.js +2 -1
  86. package/lib/views/profile/UserBadge.d.ts +18 -0
  87. package/lib/views/profile/UserBadge.js +101 -0
  88. package/lib/views/profile/index.d.ts +2 -0
  89. package/lib/views/profile/index.js +5 -0
  90. package/lib/views/secrets/Secrets.js +1 -1
  91. package/package.json +27 -3
  92. package/lib/api/spacer/agentSpaces.d.ts +0 -193
  93. package/lib/api/spacer/agentSpaces.js +0 -127
  94. package/lib/theme/DatalayerTheme.d.ts +0 -52
  95. package/lib/theme/DatalayerTheme.js +0 -228
  96. package/lib/theme/DatalayerThemeProvider.d.ts +0 -29
  97. package/lib/theme/DatalayerThemeProvider.js +0 -54
  98. package/lib/theme/Palette.d.ts +0 -4
  99. package/lib/theme/Palette.js +0 -10
  100. package/lib/theme/index.d.ts +0 -4
  101. package/lib/theme/index.js +0 -8
  102. package/lib/theme/useSystemColorMode.d.ts +0 -9
  103. package/lib/theme/useSystemColorMode.js +0 -26
@@ -0,0 +1,338 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * OtelTimelineRangeSlider – A modern dual-handle timeline range slider
8
+ * for selecting time windows over OTEL signals.
9
+ *
10
+ * Built with Primer React `Box` / `Text` + native pointer events for
11
+ * smooth drag interaction. Fully theme-aware — inherits all colors
12
+ * from the active Primer theme via CSS functional tokens.
13
+ *
14
+ * Inspired by react-timeline-range-slider (d3-scale + react-compound-slider)
15
+ * but implemented from scratch with zero external dependencies beyond Primer.
16
+ *
17
+ * @module otel/OtelTimelineRangeSlider
18
+ */
19
+ import { useCallback, useRef, useState, useMemo } from 'react';
20
+ import { Box, Text } from '@primer/react';
21
+ // ── Helpers ─────────────────────────────────────────────────────────
22
+ function clamp(v, min, max) {
23
+ return Math.max(min, Math.min(max, v));
24
+ }
25
+ function lerp(a, b, t) {
26
+ return a + (b - a) * t;
27
+ }
28
+ function defaultFormatTick(d) {
29
+ const h = d.getHours().toString().padStart(2, '0');
30
+ const m = d.getMinutes().toString().padStart(2, '0');
31
+ return `${h}:${m}`;
32
+ }
33
+ function generateTicks(start, end, count) {
34
+ const ticks = [];
35
+ const s = start.getTime();
36
+ const e = end.getTime();
37
+ for (let i = 0; i <= count; i++) {
38
+ ticks.push(new Date(lerp(s, e, i / count)));
39
+ }
40
+ return ticks;
41
+ }
42
+ // ── Component ───────────────────────────────────────────────────────
43
+ export const OtelTimelineRangeSlider = ({ timelineStart, timelineEnd, selectedStart, selectedEnd, onRangeChange, onRangeCommit, tickCount = 8, formatTick = defaultFormatTick, height = 56, histogram, }) => {
44
+ const railRef = useRef(null);
45
+ const dragging = useRef(null);
46
+ const dragOrigin = useRef({ pct: 0, startPct: 0, endPct: 0 });
47
+ const [isDragging, setIsDragging] = useState(false);
48
+ const [hoverPct, setHoverPct] = useState(null);
49
+ // Compute percentages
50
+ const tlStart = timelineStart.getTime();
51
+ const tlEnd = timelineEnd.getTime();
52
+ const tlRange = tlEnd - tlStart || 1;
53
+ const startPct = ((selectedStart.getTime() - tlStart) / tlRange) * 100;
54
+ const endPct = ((selectedEnd.getTime() - tlStart) / tlRange) * 100;
55
+ const ticks = useMemo(() => generateTicks(timelineStart, timelineEnd, tickCount), [timelineStart, timelineEnd, tickCount]);
56
+ // Histogram bars
57
+ const histMax = useMemo(() => {
58
+ if (!histogram?.length)
59
+ return 0;
60
+ return Math.max(...histogram.map(h => h.count));
61
+ }, [histogram]);
62
+ // Convert pointer position to percentage
63
+ const pointerToPct = useCallback((clientX) => {
64
+ if (!railRef.current)
65
+ return 0;
66
+ const rect = railRef.current.getBoundingClientRect();
67
+ return clamp(((clientX - rect.left) / rect.width) * 100, 0, 100);
68
+ }, []);
69
+ const pctToDate = useCallback((pct) => new Date(tlStart + (pct / 100) * tlRange), [tlStart, tlRange]);
70
+ // ── Pointer handlers ──
71
+ const handlePointerDown = useCallback((e, target) => {
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+ e.target.setPointerCapture(e.pointerId);
75
+ dragging.current = target;
76
+ const pct = pointerToPct(e.clientX);
77
+ dragOrigin.current = { pct, startPct, endPct };
78
+ setIsDragging(true);
79
+ }, [pointerToPct, startPct, endPct]);
80
+ const handlePointerMove = useCallback((e) => {
81
+ if (!dragging.current)
82
+ return;
83
+ const pct = pointerToPct(e.clientX);
84
+ const origin = dragOrigin.current;
85
+ let newStart = startPct;
86
+ let newEnd = endPct;
87
+ if (dragging.current === 'start') {
88
+ newStart = clamp(pct, 0, endPct - 1);
89
+ }
90
+ else if (dragging.current === 'end') {
91
+ newEnd = clamp(pct, startPct + 1, 100);
92
+ }
93
+ else if (dragging.current === 'track') {
94
+ const delta = pct - origin.pct;
95
+ const span = origin.endPct - origin.startPct;
96
+ let s = origin.startPct + delta;
97
+ let e = origin.endPct + delta;
98
+ if (s < 0) {
99
+ s = 0;
100
+ e = span;
101
+ }
102
+ if (e > 100) {
103
+ e = 100;
104
+ s = 100 - span;
105
+ }
106
+ newStart = s;
107
+ newEnd = e;
108
+ }
109
+ onRangeChange(pctToDate(newStart), pctToDate(newEnd));
110
+ }, [pointerToPct, startPct, endPct, onRangeChange, pctToDate]);
111
+ const handlePointerUp = useCallback((e) => {
112
+ if (!dragging.current)
113
+ return;
114
+ e.target.releasePointerCapture(e.pointerId);
115
+ dragging.current = null;
116
+ setIsDragging(false);
117
+ onRangeCommit?.(selectedStart, selectedEnd);
118
+ }, [selectedStart, selectedEnd, onRangeCommit]);
119
+ // Hover handlers – show dotted line + tooltip while not dragging
120
+ const handleMouseMove = useCallback((e) => {
121
+ if (dragging.current)
122
+ return;
123
+ setHoverPct(pointerToPct(e.clientX));
124
+ }, [pointerToPct]);
125
+ const handleMouseLeave = useCallback(() => {
126
+ setHoverPct(null);
127
+ }, []);
128
+ // Format date for hover tooltip (date + time with seconds)
129
+ const formatHoverDate = useCallback((pct) => {
130
+ const d = pctToDate(pct);
131
+ const date = d.toLocaleDateString(undefined, {
132
+ year: 'numeric',
133
+ month: 'short',
134
+ day: 'numeric',
135
+ });
136
+ const time = d.toLocaleTimeString(undefined, {
137
+ hour: '2-digit',
138
+ minute: '2-digit',
139
+ second: '2-digit',
140
+ });
141
+ return `${date} ${time}`;
142
+ }, [pctToDate]);
143
+ // Rail click to jump
144
+ const handleRailClick = useCallback((e) => {
145
+ if (isDragging)
146
+ return;
147
+ const pct = pointerToPct(e.clientX);
148
+ // Jump the closer handle
149
+ const distToStart = Math.abs(pct - startPct);
150
+ const distToEnd = Math.abs(pct - endPct);
151
+ if (distToStart < distToEnd) {
152
+ const newStart = clamp(pct, 0, endPct - 1);
153
+ onRangeChange(pctToDate(newStart), selectedEnd);
154
+ onRangeCommit?.(pctToDate(newStart), selectedEnd);
155
+ }
156
+ else {
157
+ const newEnd = clamp(pct, startPct + 1, 100);
158
+ onRangeChange(selectedStart, pctToDate(newEnd));
159
+ onRangeCommit?.(selectedStart, pctToDate(newEnd));
160
+ }
161
+ }, [
162
+ isDragging,
163
+ pointerToPct,
164
+ startPct,
165
+ endPct,
166
+ onRangeChange,
167
+ onRangeCommit,
168
+ pctToDate,
169
+ selectedStart,
170
+ selectedEnd,
171
+ ]);
172
+ const RAIL_HEIGHT = height - 24; // Reserve 24px for tick labels
173
+ const HANDLE_WIDTH = 10;
174
+ const HANDLE_HEIGHT = RAIL_HEIGHT;
175
+ return (_jsxs(Box, { sx: {
176
+ width: '100%',
177
+ height,
178
+ px: 3,
179
+ py: 1,
180
+ userSelect: 'none',
181
+ bg: 'canvas.subtle',
182
+ borderBottom: '1px solid',
183
+ borderColor: 'border.default',
184
+ }, children: [_jsxs(Box, { ref: railRef, onClick: handleRailClick, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, sx: {
185
+ position: 'relative',
186
+ width: '100%',
187
+ height: RAIL_HEIGHT,
188
+ cursor: 'pointer',
189
+ }, children: [_jsx(Box, { sx: {
190
+ position: 'absolute',
191
+ inset: 0,
192
+ bg: 'canvas.inset',
193
+ borderRadius: 1,
194
+ border: '1px solid',
195
+ borderColor: 'border.muted',
196
+ } }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'track'), sx: {
197
+ position: 'absolute',
198
+ top: 0,
199
+ bottom: 0,
200
+ left: `${startPct}%`,
201
+ width: `${endPct - startPct}%`,
202
+ bg: 'accent.muted',
203
+ opacity: 0.45,
204
+ borderTop: '2px solid',
205
+ borderBottom: '2px solid',
206
+ borderColor: 'accent.fg',
207
+ cursor: 'grab',
208
+ zIndex: 2,
209
+ transition: isDragging
210
+ ? 'none'
211
+ : 'left 0.05s ease, width 0.05s ease',
212
+ ':active': { cursor: 'grabbing' },
213
+ } }), histogram &&
214
+ histMax > 0 &&
215
+ histogram.map((h, i) => {
216
+ const pct = ((h.time.getTime() - tlStart) / tlRange) * 100;
217
+ const barWidth = 100 / (histogram.length || 1);
218
+ const barH = (h.count / histMax) * 100;
219
+ return (_jsx(Box, { sx: {
220
+ position: 'absolute',
221
+ bottom: 0,
222
+ left: `${pct}%`,
223
+ width: `${barWidth}%`,
224
+ height: `${barH}%`,
225
+ bg: 'accent.fg',
226
+ opacity: 0.35,
227
+ borderRadius: '1px 1px 0 0',
228
+ pointerEvents: 'none',
229
+ zIndex: 4,
230
+ } }, i));
231
+ }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'start'), sx: {
232
+ position: 'absolute',
233
+ top: 0,
234
+ left: `${startPct}%`,
235
+ width: HANDLE_WIDTH,
236
+ height: HANDLE_HEIGHT,
237
+ ml: `-${HANDLE_WIDTH / 2}px`,
238
+ bg: 'accent.fg',
239
+ borderRadius: 1,
240
+ cursor: 'ew-resize',
241
+ zIndex: 3,
242
+ display: 'flex',
243
+ alignItems: 'center',
244
+ justifyContent: 'center',
245
+ transition: isDragging ? 'none' : 'left 0.05s ease',
246
+ boxShadow: 'shadow.medium',
247
+ ':hover': { bg: 'accent.emphasis' },
248
+ }, children: _jsx(Box, { sx: {
249
+ width: '2px',
250
+ height: '40%',
251
+ bg: 'fg.onEmphasis',
252
+ borderRadius: 1,
253
+ } }) }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'end'), sx: {
254
+ position: 'absolute',
255
+ top: 0,
256
+ left: `${endPct}%`,
257
+ width: HANDLE_WIDTH,
258
+ height: HANDLE_HEIGHT,
259
+ ml: `-${HANDLE_WIDTH / 2}px`,
260
+ bg: 'accent.fg',
261
+ borderRadius: 1,
262
+ cursor: 'ew-resize',
263
+ zIndex: 3,
264
+ display: 'flex',
265
+ alignItems: 'center',
266
+ justifyContent: 'center',
267
+ transition: isDragging ? 'none' : 'left 0.05s ease',
268
+ boxShadow: 'shadow.medium',
269
+ ':hover': { bg: 'accent.emphasis' },
270
+ }, children: _jsx(Box, { sx: {
271
+ width: '2px',
272
+ height: '40%',
273
+ bg: 'fg.onEmphasis',
274
+ borderRadius: 1,
275
+ } }) }), hoverPct !== null && !isDragging && (_jsxs(_Fragment, { children: [_jsx(Box, { sx: {
276
+ position: 'absolute',
277
+ top: 0,
278
+ left: `${hoverPct}%`,
279
+ width: '1px',
280
+ height: RAIL_HEIGHT,
281
+ borderLeft: '1px dashed',
282
+ borderColor: 'fg.default',
283
+ opacity: 0.55,
284
+ pointerEvents: 'none',
285
+ zIndex: 5,
286
+ } }), _jsx(Box, { sx: {
287
+ position: 'absolute',
288
+ top: -36,
289
+ left: `${hoverPct}%`,
290
+ transform: 'translateX(-50%)',
291
+ bg: 'canvas.overlay',
292
+ border: '1px solid',
293
+ borderColor: 'border.default',
294
+ borderRadius: 2,
295
+ px: 2,
296
+ py: '2px',
297
+ pointerEvents: 'none',
298
+ zIndex: 10,
299
+ boxShadow: 'shadow.medium',
300
+ whiteSpace: 'nowrap',
301
+ }, children: _jsx(Text, { sx: {
302
+ fontSize: '11px',
303
+ color: 'fg.default',
304
+ fontFamily: 'mono',
305
+ lineHeight: '18px',
306
+ }, children: formatHoverDate(hoverPct) }) })] })), ticks.map((t, i) => {
307
+ const pct = ((t.getTime() - tlStart) / tlRange) * 100;
308
+ return (_jsx(Box, { sx: {
309
+ position: 'absolute',
310
+ top: 0,
311
+ left: `${pct}%`,
312
+ width: '1px',
313
+ height: RAIL_HEIGHT,
314
+ bg: 'border.muted',
315
+ pointerEvents: 'none',
316
+ opacity: 0.5,
317
+ zIndex: 1,
318
+ } }, i));
319
+ })] }), _jsx(Box, { sx: {
320
+ position: 'relative',
321
+ width: '100%',
322
+ height: 20,
323
+ mt: '2px',
324
+ }, children: ticks.map((t, i) => {
325
+ const pct = ((t.getTime() - tlStart) / tlRange) * 100;
326
+ return (_jsx(Text, { sx: {
327
+ position: 'absolute',
328
+ left: `${pct}%`,
329
+ transform: 'translateX(-50%)',
330
+ fontSize: '10px',
331
+ color: 'fg.subtle',
332
+ whiteSpace: 'nowrap',
333
+ fontFamily: 'mono',
334
+ lineHeight: '16px',
335
+ pointerEvents: 'none',
336
+ }, children: formatTick(t) }, i));
337
+ }) })] }));
338
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OtelTracesList – Tabular list of spans with Time / Message / Scope / Duration
3
+ * columns, using Primer React components for consistent theming.
4
+ *
5
+ * Spans that share the same trace_id are grouped into collapsible trees:
6
+ * only root spans are shown initially; clicking a row with children
7
+ * expands / collapses the nested child rows (shown indented).
8
+ *
9
+ * @module otel/OtelTracesList
10
+ */
11
+ import React from 'react';
12
+ import type { OtelTracesListProps } from './types';
13
+ export declare const OtelTracesList: React.FC<OtelTracesListProps>;
@@ -0,0 +1,199 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * OtelTracesList – Tabular list of spans with Time / Message / Scope / Duration
8
+ * columns, using Primer React components for consistent theming.
9
+ *
10
+ * Spans that share the same trace_id are grouped into collapsible trees:
11
+ * only root spans are shown initially; clicking a row with children
12
+ * expands / collapses the nested child rows (shown indented).
13
+ *
14
+ * @module otel/OtelTracesList
15
+ */
16
+ import { useMemo, useCallback, useState } from 'react';
17
+ import { Box, Text, Label, Spinner } from '@primer/react';
18
+ import { Blankslate } from '@primer/react/experimental';
19
+ import { TelescopeIcon, ChevronRightIcon, ChevronDownIcon, } from '@primer/octicons-react';
20
+ import { formatDuration, formatTime, buildSpanTree } from './utils';
21
+ const GRID_COLS = '140px 1fr 160px 90px';
22
+ // ── Helpers ─────────────────────────────────────────────────────────
23
+ /** Extract gen_ai token counts from span attributes. */
24
+ function getTokenUsage(span) {
25
+ const attrs = span.attributes;
26
+ if (!attrs)
27
+ return null;
28
+ const input = attrs['gen_ai.usage.input_tokens'] != null
29
+ ? Number(attrs['gen_ai.usage.input_tokens'])
30
+ : undefined;
31
+ const output = attrs['gen_ai.usage.output_tokens'] != null
32
+ ? Number(attrs['gen_ai.usage.output_tokens'])
33
+ : undefined;
34
+ if (input == null && output == null)
35
+ return null;
36
+ return { input, output };
37
+ }
38
+ export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }) => {
39
+ // Set of expanded span_ids (show their children)
40
+ const [expanded, setExpanded] = useState(new Set());
41
+ // Build the span tree from flat input
42
+ const roots = useMemo(() => buildSpanTree(spans), [spans]);
43
+ // Flatten visible rows based on which nodes are expanded
44
+ const visibleRows = useMemo(() => {
45
+ const rows = [];
46
+ function walk(node, depth) {
47
+ const children = node.children ?? [];
48
+ rows.push({ span: node, depth, hasChildren: children.length > 0 });
49
+ if (expanded.has(node.span_id)) {
50
+ for (const child of children) {
51
+ walk(child, depth + 1);
52
+ }
53
+ }
54
+ }
55
+ for (const root of roots) {
56
+ walk(root, 0);
57
+ }
58
+ return rows;
59
+ }, [roots, expanded]);
60
+ const toggleExpand = useCallback((spanId) => {
61
+ setExpanded(prev => {
62
+ const next = new Set(prev);
63
+ if (next.has(spanId)) {
64
+ next.delete(spanId);
65
+ }
66
+ else {
67
+ next.add(spanId);
68
+ }
69
+ return next;
70
+ });
71
+ }, []);
72
+ if (loading) {
73
+ return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 5 }, children: _jsx(Spinner, { size: "medium" }) }));
74
+ }
75
+ if (spans.length === 0) {
76
+ return (_jsxs(Blankslate, { children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: 24 }) }), _jsx(Blankslate.Heading, { children: "No traces found" }), _jsx(Blankslate.Description, { children: "Send some telemetry data first." })] }));
77
+ }
78
+ return (_jsxs(Box, { sx: { width: '100%', flex: 1, minHeight: 0, overflow: 'auto' }, children: [_jsx(Box, { sx: {
79
+ display: 'grid',
80
+ gridTemplateColumns: GRID_COLS,
81
+ gap: 2,
82
+ px: 3,
83
+ position: 'sticky',
84
+ top: 0,
85
+ bg: 'canvas.subtle',
86
+ zIndex: 1,
87
+ borderBottom: '2px solid',
88
+ borderColor: 'border.default',
89
+ }, children: ['Time', 'Message', 'Scope', 'Duration'].map(h => (_jsx(Text, { sx: {
90
+ fontWeight: 'bold',
91
+ fontSize: 0,
92
+ color: 'fg.muted',
93
+ textTransform: 'uppercase',
94
+ letterSpacing: '0.05em',
95
+ py: 2,
96
+ ...(h === 'Duration' ? { textAlign: 'right' } : {}),
97
+ }, children: h }, h))) }), visibleRows.map(({ span, depth, hasChildren }, idx) => {
98
+ const isSelected = selectedSpanId === span.span_id;
99
+ const isExpanded = expanded.has(span.span_id);
100
+ const indent = depth * 20;
101
+ return (_jsxs(Box, { onClick: () => {
102
+ if (hasChildren) {
103
+ toggleExpand(span.span_id);
104
+ }
105
+ onSelectSpan?.(span);
106
+ }, sx: {
107
+ display: 'grid',
108
+ gridTemplateColumns: GRID_COLS,
109
+ gap: 2,
110
+ px: 3,
111
+ py: '5px',
112
+ cursor: 'pointer',
113
+ bg: isSelected
114
+ ? 'accent.subtle'
115
+ : depth > 0
116
+ ? 'canvas.inset'
117
+ : 'canvas.default',
118
+ borderBottom: '1px solid',
119
+ borderColor: 'border.muted',
120
+ ':hover': {
121
+ bg: isSelected ? 'accent.subtle' : 'canvas.subtle',
122
+ },
123
+ }, children: [_jsx(Text, { sx: {
124
+ fontSize: 1,
125
+ fontFamily: 'mono',
126
+ color: 'fg.muted',
127
+ whiteSpace: 'nowrap',
128
+ lineHeight: '22px',
129
+ }, children: formatTime(span.start_time) }), _jsxs(Box, { sx: {
130
+ display: 'flex',
131
+ alignItems: 'center',
132
+ gap: 1,
133
+ overflow: 'hidden',
134
+ lineHeight: '22px',
135
+ pl: `${indent}px`,
136
+ }, children: [hasChildren ? (_jsx(Box, { sx: {
137
+ flexShrink: 0,
138
+ color: 'fg.muted',
139
+ display: 'flex',
140
+ alignItems: 'center',
141
+ }, children: isExpanded ? (_jsx(ChevronDownIcon, { size: 14 })) : (_jsx(ChevronRightIcon, { size: 14 })) })) : depth > 0 ? (
142
+ /* Connector dash for child leaves */
143
+ _jsx(Box, { sx: {
144
+ flexShrink: 0,
145
+ width: '14px',
146
+ display: 'flex',
147
+ alignItems: 'center',
148
+ justifyContent: 'center',
149
+ color: 'fg.subtle',
150
+ fontSize: 0,
151
+ }, children: "\u2500" })) : null, _jsx(Text, { sx: {
152
+ fontSize: 1,
153
+ overflow: 'hidden',
154
+ textOverflow: 'ellipsis',
155
+ whiteSpace: 'nowrap',
156
+ }, title: span.span_name, children: span.span_name }), span.status_code === 'ERROR' && (_jsx(Label, { variant: "danger", size: "small", children: "ERROR" })), (() => {
157
+ const usage = getTokenUsage(span);
158
+ if (!usage)
159
+ return null;
160
+ return (_jsxs(Box, { sx: {
161
+ display: 'inline-flex',
162
+ alignItems: 'center',
163
+ gap: '2px',
164
+ border: '1px solid',
165
+ borderColor: 'border.default',
166
+ borderRadius: 2,
167
+ px: 1,
168
+ fontSize: '10px',
169
+ fontFamily: 'mono',
170
+ color: 'fg.muted',
171
+ flexShrink: 0,
172
+ lineHeight: '16px',
173
+ }, children: [usage.input != null && (_jsxs(Box, { sx: {
174
+ display: 'inline-flex',
175
+ alignItems: 'center',
176
+ gap: '1px',
177
+ }, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { sx: {
178
+ display: 'inline-flex',
179
+ alignItems: 'center',
180
+ gap: '1px',
181
+ }, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
182
+ })(), span.otel_scope_name && (_jsx(Label, { size: "small", variant: "secondary", sx: { flexShrink: 0, fontSize: '10px' }, children: span.otel_scope_name }))] }), _jsx(Text, { sx: {
183
+ fontSize: 1,
184
+ color: 'fg.muted',
185
+ overflow: 'hidden',
186
+ textOverflow: 'ellipsis',
187
+ whiteSpace: 'nowrap',
188
+ lineHeight: '22px',
189
+ }, title: `${span.service_name} / ${span.otel_scope_name ?? ''}`, children: span.service_name }), _jsx(Text, { sx: {
190
+ fontSize: 1,
191
+ fontFamily: 'mono',
192
+ textAlign: 'right',
193
+ lineHeight: '22px',
194
+ color: span.duration_ms != null && span.duration_ms > 1000
195
+ ? 'danger.fg'
196
+ : 'fg.default',
197
+ }, children: formatDuration(span.duration_ms) })] }, `${span.trace_id}-${span.span_id}-${idx}`));
198
+ })] }));
199
+ };