@datalayer/core 1.2.27 → 1.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/evals/__tests__/external-definition.golden.json +110 -0
- package/lib/api/evals/client.d.ts +13 -1
- package/lib/api/evals/client.js +13 -0
- package/lib/api/evals/types.d.ts +168 -1
- package/lib/api/mcp/agentAccess.d.ts +10 -0
- package/lib/api/mcp/agentAccess.js +28 -0
- package/lib/api/mcp/generated.js +20 -0
- package/lib/api/mcp/index.d.ts +1 -0
- package/lib/api/mcp/index.js +1 -0
- package/lib/api/mcp/observability.d.ts +1 -1
- package/lib/api/mcp/observability.js +2 -0
- package/lib/api/mcp/operations.d.ts +28 -10
- package/lib/api/mcp/tasks.d.ts +9 -1
- package/lib/api/otel/firewall.d.ts +74 -0
- package/lib/api/otel/firewall.js +42 -0
- package/lib/api/otel/index.d.ts +1 -0
- package/lib/api/otel/index.js +1 -0
- package/lib/cli-login.js +2 -2
- package/lib/components/anonymous/AnonymousKeyExpired.js +15 -3
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/mcp/ScopeList.js +10 -1
- package/lib/components/principal/PrincipalSwitcherMenu.js +4 -4
- package/lib/components/sharing/ShareAccessComponent.js +33 -8
- package/lib/components/toast/ToastPanel.d.ts +11 -0
- package/lib/components/toast/ToastPanel.js +42 -0
- package/lib/components/toast/index.d.ts +2 -0
- package/lib/components/toast/index.js +6 -0
- package/lib/components/toast/toastQueue.d.ts +35 -0
- package/lib/components/toast/toastQueue.js +47 -0
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useCache.d.ts +4 -2
- package/lib/hooks/useCache.js +13 -4
- package/lib/hooks/useDatalayer.js +12 -16
- package/lib/hooks/useKeptScroll.d.ts +6 -0
- package/lib/hooks/useKeptScroll.js +54 -0
- package/lib/hooks/useMcp.d.ts +2 -2
- package/lib/hooks/useMcp.js +5 -2
- package/lib/hooks/useNavigate.d.ts +16 -2
- package/lib/hooks/useNavigate.js +65 -5
- package/lib/hooks/useToast.d.ts +6 -0
- package/lib/hooks/useToast.js +40 -1
- package/lib/models/McpAgentAccess.d.ts +95 -0
- package/lib/models/McpAgentAccess.js +5 -0
- package/lib/models/Usage.d.ts +13 -0
- package/lib/models/Usage.js +5 -0
- package/lib/models/index.d.ts +1 -0
- package/lib/models/index.js +1 -0
- package/lib/otel/client/OtelClient.d.ts +56 -0
- package/lib/otel/client/OtelClient.js +25 -0
- package/lib/otel/client/index.d.ts +1 -1
- package/lib/otel/index.d.ts +1 -1
- package/lib/otel/views/OtelLive.js +3 -3
- package/lib/otel/views/OtelLiveSpans.js +2 -2
- package/lib/otel/views/OtelLogsList.js +3 -3
- package/lib/otel/views/OtelMetricsChart.d.ts +13 -0
- package/lib/otel/views/OtelMetricsChart.js +109 -26
- package/lib/otel/views/OtelSpanDetail.js +3 -3
- package/lib/otel/views/OtelSqlView.js +4 -4
- package/lib/otel/views/OtelSystemView.js +2 -2
- package/lib/otel/views/OtelTimelineRangeSlider.js +2 -2
- package/lib/otel/views/OtelTracesList.js +2 -2
- package/lib/state/substates/LayoutState.js +24 -3
- package/lib/state/substates/NavigationState.d.ts +11 -3
- package/lib/state/substates/NavigationState.js +48 -5
- package/lib/views/mcp/AuditLog.js +1 -1
- package/lib/views/mcp/ConnectedAgents.d.ts +1 -1
- package/lib/views/mcp/ConnectedAgents.js +13 -10
- package/lib/views/mcp/McpDashboard.d.ts +1 -1
- package/lib/views/mcp/McpDashboard.js +6 -6
- package/lib/views/mcp/McpHome.d.ts +3 -1
- package/lib/views/mcp/McpHome.js +19 -9
- package/lib/views/mcp/McpObservability.d.ts +22 -3
- package/lib/views/mcp/McpObservability.js +65 -17
- package/lib/views/mcp/Policies.js +1 -1
- package/lib/views/mcp/RunDetail.js +2 -2
- package/lib/views/mcp/types.d.ts +8 -4
- package/lib/views/otel/OtelHeader.js +2 -2
- package/package.json +2 -2
|
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
13
13
|
* @module otel/OtelLive
|
|
14
14
|
*/
|
|
15
15
|
import { useState, useCallback, useMemo, useEffect, useRef, } from 'react';
|
|
16
|
-
import { Box } from '@datalayer/primer-addons';
|
|
16
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
17
17
|
import { Text, Button, Label } from '@primer/react';
|
|
18
18
|
import { GitBranchIcon, ClockIcon } from '@primer/octicons-react';
|
|
19
19
|
import { getOtelConsumeUrl } from '../../state/substates/CoreState.js';
|
|
@@ -288,11 +288,11 @@ export const OtelLive = ({ baseUrl = getOtelConsumeUrl(), wsBaseUrl, token, auto
|
|
|
288
288
|
return ts >= s && ts <= e;
|
|
289
289
|
});
|
|
290
290
|
}, [metrics, isRangeActive, rangeStart, rangeEnd]);
|
|
291
|
-
return (_jsxs(
|
|
291
|
+
return (_jsxs(Card, { border: true, shadow: "none", display: "flex", flexDirection: "column", flex: 1, minHeight: 0, height: "100%", color: "fg.default", overflow: "hidden", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 0, children: [_jsx(Box, { flex: 1, children: _jsx(OtelSearchBar, { signal: signal, onSignalChange: setSignal, services: effectiveServices, selectedService: service, onServiceChange: setService, query: query, onQueryChange: setQuery, onRefresh: handleRefresh, loading: signal === 'traces'
|
|
292
292
|
? tracesLoading
|
|
293
293
|
: signal === 'logs'
|
|
294
294
|
? logsLoading
|
|
295
|
-
: metricsLoading }) }), _jsx(Box, { pr: 2, flexShrink: 0, children: _jsx(Label, { variant:
|
|
295
|
+
: metricsLoading }) }), wsConnected ? null : (_jsx(Box, { pr: 2, flexShrink: 0, children: _jsx(Label, { variant: "secondary", size: "small", children: "\u25CB Polling" }) }))] }), timelineBounds && rangeStart && rangeEnd && (_jsx(Box, { px: 3, pt: 2, pb: 1, borderBottom: "1px solid", borderColor: "border.default", bg: "canvas.subtle", flexShrink: 0, position: "relative", zIndex: 0, children: _jsx(OtelTimelineRangeSlider, { timelineStart: timelineBounds.start, timelineEnd: timelineBounds.end, selectedStart: rangeStart, selectedEnd: rangeEnd, onRangeChange: handleRangeChange, histogram: histogram, height: 48, tickCount: 6 }) })), _jsxs(Box, { display: "flex", flex: 1, minHeight: 0, overflow: "hidden", children: [_jsxs(Box, { display: "flex", flexDirection: "column", flex: hasDetail ? '0 0 55%' : '1 1 100%', minHeight: 0, overflow: "hidden", children: [signal === 'traces' && (_jsx(OtelTracesList, { spans: filteredTraces, loading: tracesLoading, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), signal === 'logs' && (_jsx(OtelLogsList, { logs: filteredLogs, loading: logsLoading, selectedLogIndex: selectedLogIdx, onSelectLog: handleLogSelect })), signal === 'metrics' && (_jsx(OtelMetricsList, { metrics: filteredMetrics, loading: metricsLoading }))] }), hasDetail && (_jsxs(Box, { flex: "0 0 45%", minHeight: 0, overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", children: [signal === 'traces' && selectedSpan && (_jsx(OtelSpanDetail, { span: selectedSpan, traceSpans: traceSpans ?? undefined, onClose: handleCloseDetail })), signal === 'logs' && selectedLogIdx !== null && logs && (_jsxs(Box, { p: 3, children: [_jsxs(Box, { display: "flex", justifyContent: "space-between", mb: 3, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 2 }, children: "Log Detail" }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleCloseDetail, children: "\u2715" })] }), _jsx(Box, { as: "pre", m: 0, fontSize: 1, fontFamily: "mono", whiteSpace: "pre-wrap", wordBreak: "break-word", children: JSON.stringify(logs[selectedLogIdx], null, 2) })] }))] }))] }), signal === 'traces' && selectedSpan && spanTree.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "flex", gap: 1, px: 3, py: 1, bg: "canvas.subtle", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [_jsx(Button, { size: "small", variant: bottomPane === 'timeline' ? 'primary' : 'invisible', leadingVisual: ClockIcon, onClick: () => toggleBottomPane('timeline'), children: "Timeline" }), _jsx(Button, { size: "small", variant: bottomPane === 'tree' ? 'primary' : 'invisible', leadingVisual: GitBranchIcon, onClick: () => toggleBottomPane('tree'), children: "Span Tree" })] }), bottomPane && (_jsxs(Box, { height: 260, overflow: "auto", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [bottomPane === 'timeline' && (_jsx(OtelTimeline, { spans: traceSpans ?? [], selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), bottomPane === 'tree' && (_jsx(OtelSpanTree, { spans: spanTree, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect, defaultExpandDepth: 3 }))] }))] }))] }));
|
|
296
296
|
};
|
|
297
297
|
// ── Client-side filter helpers ──────────────────────────────────────
|
|
298
298
|
function filterSpans(spans, q) {
|
|
@@ -16,7 +16,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
16
16
|
* @module otel/views/OtelLiveSpans
|
|
17
17
|
*/
|
|
18
18
|
import { useMemo, useState } from 'react';
|
|
19
|
-
import { Box } from '@datalayer/primer-addons';
|
|
19
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
20
20
|
import { ActionList, ActionMenu, Button, Text } from '@primer/react';
|
|
21
21
|
import { DownloadIcon, PauseIcon, PlayIcon, TrashIcon, } from '@primer/octicons-react';
|
|
22
22
|
import { toMs } from '../utils.js';
|
|
@@ -94,7 +94,7 @@ export const OtelLiveSpans = ({ tracer, spans: given, service, facets = DEFAULT_
|
|
|
94
94
|
? everything.filter(span => span.trace_id === selectedSpan.trace_id)
|
|
95
95
|
: [], [everything, selectedSpan]);
|
|
96
96
|
const side = detailPlacement === 'side' && !compact;
|
|
97
|
-
return (_jsxs(
|
|
97
|
+
return (_jsxs(Card, { border: true, shadow: "none", display: "block", "data-otel-live-spans": "", color: "fg.default", minWidth: 0, children: [_jsxs(Box, { role: "toolbar", "aria-label": label, display: "flex", flexWrap: "wrap", gap: 2, alignItems: "center", p: 2, borderBottom: "1px solid", borderColor: "border.default", children: [facets.map((facet, index) => (_jsx(FacetMenu, { facet: facet, options: options[index] ?? [], selected: selected[index] ?? new Set(), onChange: next => setSelected(prev => facets.map((_, at) => at === index ? next : (prev[at] ?? new Set()))) }, facet.label))), _jsx(Box, { flex: 1 }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, "data-otel-span-count": "", children: [rows.length === shown.length
|
|
98
98
|
? `${rows.length} ${rows.length === 1 ? 'span' : 'spans'}`
|
|
99
99
|
: `${rows.length} of ${shown.length}`, waiting > 0 ? ` · ${waiting} new` : ''] }), _jsx(Button, { size: "small", leadingVisual: held ? PlayIcon : PauseIcon, "aria-pressed": held !== null, onClick: () => setHeld(held ? null : recorded), "data-otel-pause": "", children: held ? 'Resume' : 'Pause' }), _jsx(Button, { size: "small", leadingVisual: TrashIcon, onClick: () => {
|
|
100
100
|
setHeld(null);
|
|
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
12
12
|
* @module otel/OtelLogsList
|
|
13
13
|
*/
|
|
14
14
|
import React, { useState } from 'react';
|
|
15
|
-
import { Box } from '@datalayer/primer-addons';
|
|
15
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
16
16
|
import { Text, Label, Spinner } from '@primer/react';
|
|
17
17
|
import { Blankslate } from '@primer/react/experimental';
|
|
18
18
|
import { LogIcon } from '@primer/octicons-react';
|
|
@@ -27,13 +27,13 @@ const LogDetail = ({ log }) => (_jsxs(Box, { gridColumn: "1 / -1", bg: "canvas.s
|
|
|
27
27
|
color: 'fg.muted',
|
|
28
28
|
display: 'block',
|
|
29
29
|
mb: 1,
|
|
30
|
-
}, children: "Body" }), _jsx(
|
|
30
|
+
}, children: "Body" }), _jsx(Card, { border: true, shadow: "none", display: "block", as: "pre", fontSize: 1, fontFamily: "mono", whiteSpace: "pre-wrap", wordBreak: "break-word", m: 0, p: 2, children: log.body })] }), log.trace_id && (_jsxs(Box, { display: "flex", gap: 3, mb: 2, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontWeight: 'bold' }, children: "trace_id" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: log.trace_id }), log.span_id && (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontWeight: 'bold' }, children: "span_id" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: log.span_id })] }))] })), log.attributes && Object.keys(log.attributes).length > 0 && (_jsxs(Box, { children: [_jsx(Text, { sx: {
|
|
31
31
|
fontSize: 0,
|
|
32
32
|
fontWeight: 'bold',
|
|
33
33
|
color: 'fg.muted',
|
|
34
34
|
display: 'block',
|
|
35
35
|
mb: 1,
|
|
36
|
-
}, children: "Attributes" }), _jsx(
|
|
36
|
+
}, children: "Attributes" }), _jsx(Card, { border: true, shadow: "none", display: "block", p: 2, children: Object.entries(log.attributes).map(([k, v]) => (_jsxs(Box, { display: "flex", gap: 2, py: 1, borderBottom: "1px solid", borderColor: "border.muted", children: [_jsx(Text, { sx: {
|
|
37
37
|
color: 'accent.fg',
|
|
38
38
|
fontSize: 0,
|
|
39
39
|
fontFamily: 'mono',
|
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import React from 'react';
|
|
15
15
|
import type { OtelMetric } from '../types';
|
|
16
|
+
/**
|
|
17
|
+
* The colours ECharts draws its text, axes, grid lines and tooltip with.
|
|
18
|
+
* A canvas reads no CSS variable, so they are read off a probe the theme
|
|
19
|
+
* colours (fg.default, fg.muted, border.muted, canvas.overlay), resolved to
|
|
20
|
+
* what the colour mode makes them: hard-coded, the axes and the tooltip were
|
|
21
|
+
* light-mode only and unreadable in the dark (28 October 2026).
|
|
22
|
+
*/
|
|
23
|
+
export interface ChartInk {
|
|
24
|
+
text: string;
|
|
25
|
+
muted: string;
|
|
26
|
+
line: string;
|
|
27
|
+
surface: string;
|
|
28
|
+
}
|
|
16
29
|
export interface OtelMetricsChartProps {
|
|
17
30
|
metrics: OtelMetric[];
|
|
18
31
|
/** Height per chart panel in px. Default 240. */
|
|
@@ -1,4 +1,22 @@
|
|
|
1
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
|
+
* OtelMetricsChart – Type-aware ECharts visualisation for OTEL metrics.
|
|
8
|
+
*
|
|
9
|
+
* Renders one chart section per `metric_type` (`sum`, `histogram`, `gauge`,
|
|
10
|
+
* and any other type present) with an appropriate chart style:
|
|
11
|
+
*
|
|
12
|
+
* - **sum** (counters): area-line chart with gradient fill
|
|
13
|
+
* - **histogram**: bar chart with grouped series
|
|
14
|
+
* - **gauge**: plain line chart (no area fill)
|
|
15
|
+
* - **other**: falls back to area-line
|
|
16
|
+
*
|
|
17
|
+
* @module otel/OtelMetricsChart
|
|
18
|
+
*/
|
|
19
|
+
import React, { useLayoutEffect, useRef, useState } from 'react';
|
|
2
20
|
import * as echarts from 'echarts';
|
|
3
21
|
import ReactECharts from 'echarts-for-react';
|
|
4
22
|
import { Box } from '@datalayer/primer-addons';
|
|
@@ -108,30 +126,90 @@ const TYPE_ORDER = {
|
|
|
108
126
|
gauge: 2,
|
|
109
127
|
exponentialHistogram: 3,
|
|
110
128
|
};
|
|
129
|
+
/** Light-mode values, until the probe has been read. */
|
|
130
|
+
const DEFAULT_INK = {
|
|
131
|
+
text: '#1f2328',
|
|
132
|
+
muted: '#59636e',
|
|
133
|
+
line: '#d1d9e0',
|
|
134
|
+
surface: '#ffffff',
|
|
135
|
+
};
|
|
136
|
+
function useChartInk() {
|
|
137
|
+
const probe = useRef(null);
|
|
138
|
+
const [ink, setInk] = useState(DEFAULT_INK);
|
|
139
|
+
useLayoutEffect(() => {
|
|
140
|
+
const node = probe.current;
|
|
141
|
+
if (!node || typeof getComputedStyle === 'undefined') {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const read = () => {
|
|
145
|
+
const style = getComputedStyle(node);
|
|
146
|
+
const muted = node.firstElementChild
|
|
147
|
+
? getComputedStyle(node.firstElementChild).color
|
|
148
|
+
: style.color;
|
|
149
|
+
const next = {
|
|
150
|
+
text: style.color || DEFAULT_INK.text,
|
|
151
|
+
muted: muted || DEFAULT_INK.muted,
|
|
152
|
+
line: style.borderTopColor || DEFAULT_INK.line,
|
|
153
|
+
surface: style.backgroundColor || DEFAULT_INK.surface,
|
|
154
|
+
};
|
|
155
|
+
setInk(current => current.text === next.text &&
|
|
156
|
+
current.muted === next.muted &&
|
|
157
|
+
current.line === next.line &&
|
|
158
|
+
current.surface === next.surface
|
|
159
|
+
? current
|
|
160
|
+
: next);
|
|
161
|
+
};
|
|
162
|
+
read();
|
|
163
|
+
// The colour mode changes on the document's attributes.
|
|
164
|
+
const observer = new MutationObserver(read);
|
|
165
|
+
observer.observe(document.documentElement, {
|
|
166
|
+
attributes: true,
|
|
167
|
+
subtree: false,
|
|
168
|
+
});
|
|
169
|
+
observer.observe(document.body, { attributes: true, subtree: false });
|
|
170
|
+
return () => observer.disconnect();
|
|
171
|
+
});
|
|
172
|
+
return [probe, ink];
|
|
173
|
+
}
|
|
174
|
+
/** The probe the ink is read off: drawn, never seen. */
|
|
175
|
+
const InkProbe = React.forwardRef((_, ref) => (_jsx(Box, { ref: ref, "aria-hidden": true, color: "fg.default", bg: "canvas.overlay", borderTop: "1px solid", borderColor: "border.muted", sx: {
|
|
176
|
+
position: 'absolute',
|
|
177
|
+
width: 0,
|
|
178
|
+
height: 0,
|
|
179
|
+
overflow: 'hidden',
|
|
180
|
+
pointerEvents: 'none',
|
|
181
|
+
}, children: _jsx(Box, { as: "span", color: "fg.muted" }) })));
|
|
182
|
+
InkProbe.displayName = 'InkProbe';
|
|
111
183
|
// ── Build ECharts option per type ──────────────────────────────────
|
|
112
|
-
function buildOption(mtype, nameMap) {
|
|
184
|
+
function buildOption(mtype, nameMap, ink = DEFAULT_INK) {
|
|
113
185
|
const names = [...nameMap.keys()];
|
|
114
186
|
const baseLegend = {
|
|
115
187
|
data: names,
|
|
116
188
|
top: 6,
|
|
117
|
-
textStyle: { fontSize: 11 },
|
|
189
|
+
textStyle: { fontSize: 11, color: ink.muted },
|
|
118
190
|
};
|
|
119
191
|
const baseGrid = { left: 50, right: 20, top: 40, bottom: 30 };
|
|
120
192
|
const baseTooltip = {
|
|
121
193
|
trigger: 'axis',
|
|
122
|
-
axisPointer: {
|
|
194
|
+
axisPointer: {
|
|
195
|
+
type: 'cross',
|
|
196
|
+
label: { backgroundColor: ink.muted, color: ink.surface },
|
|
197
|
+
},
|
|
198
|
+
backgroundColor: ink.surface,
|
|
199
|
+
borderColor: ink.line,
|
|
200
|
+
textStyle: { color: ink.text },
|
|
123
201
|
};
|
|
124
202
|
const baseXAxis = {
|
|
125
203
|
type: 'time',
|
|
126
204
|
boundaryGap: false,
|
|
127
|
-
axisLine: { lineStyle: { color:
|
|
128
|
-
axisLabel: { fontSize: 10 },
|
|
205
|
+
axisLine: { lineStyle: { color: ink.line } },
|
|
206
|
+
axisLabel: { fontSize: 10, color: ink.muted },
|
|
129
207
|
};
|
|
130
208
|
const baseYAxis = {
|
|
131
209
|
type: 'value',
|
|
132
|
-
splitLine: { lineStyle: { type: 'dashed', color:
|
|
210
|
+
splitLine: { lineStyle: { type: 'dashed', color: ink.line } },
|
|
133
211
|
axisLine: { show: false },
|
|
134
|
-
axisLabel: { fontSize: 10 },
|
|
212
|
+
axisLabel: { fontSize: 10, color: ink.muted },
|
|
135
213
|
};
|
|
136
214
|
if (mtype === 'histogram' || mtype === 'exponentialHistogram') {
|
|
137
215
|
// ── Bar chart ──
|
|
@@ -257,29 +335,34 @@ function formatDuration(ms) {
|
|
|
257
335
|
}
|
|
258
336
|
// ── Component ───────────────────────────────────────────────────────
|
|
259
337
|
export const OtelMetricsChart = ({ metrics, height = 240, }) => {
|
|
338
|
+
const [probe, ink] = useChartInk();
|
|
260
339
|
if (!metrics || metrics.length === 0) {
|
|
261
|
-
return (_jsx(ReactECharts, { echarts: echarts, option: {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
340
|
+
return (_jsxs(Box, { position: "relative", children: [_jsx(InkProbe, { ref: probe }), _jsx(ReactECharts, { echarts: echarts, option: {
|
|
341
|
+
title: {
|
|
342
|
+
text: 'No metrics data',
|
|
343
|
+
left: 'center',
|
|
344
|
+
top: 'center',
|
|
345
|
+
textStyle: {
|
|
346
|
+
color: ink.muted,
|
|
347
|
+
fontSize: 14,
|
|
348
|
+
fontWeight: 'normal',
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
}, style: { width: '100%', height: `${height}px` }, notMerge: true })] }));
|
|
269
352
|
}
|
|
270
353
|
const byType = groupByType(metrics);
|
|
271
354
|
// Sort types in a stable, user-friendly order.
|
|
272
355
|
const sortedTypes = [...byType.keys()].sort((a, b) => (TYPE_ORDER[a] ?? 99) - (TYPE_ORDER[b] ?? 99));
|
|
273
|
-
return (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
356
|
+
return (_jsxs(Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2, position: "relative", children: [_jsx(InkProbe, { ref: probe }), sortedTypes.map(mtype => {
|
|
357
|
+
const nameMap = byType.get(mtype);
|
|
358
|
+
const option = buildOption(mtype, nameMap, ink);
|
|
359
|
+
const stats = computeStats(nameMap);
|
|
360
|
+
const durationMs = stats.startTs !== null && stats.endTs !== null
|
|
361
|
+
? stats.endTs - stats.startTs
|
|
362
|
+
: null;
|
|
363
|
+
return (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, px: 1, mb: 1, children: [_jsx(Label, { size: "small", variant: typeVariant(mtype), children: mtype }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'fg.muted' }, children: typeTitle(mtype) }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["(", [...nameMap.keys()].join(', '), ")"] })] }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 3, px: 1, mb: 1, children: [_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Sum:" }), ' ', stats.total.toLocaleString(undefined, {
|
|
364
|
+
maximumFractionDigits: 4,
|
|
365
|
+
})] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Points:" }), ' ', stats.count] }), stats.startTs !== null && stats.endTs !== null && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'bold', color: 'fg.default' }, children: "Interval:" }), ' ', formatTs(stats.startTs), " \u2192 ", formatTs(stats.endTs), durationMs !== null && durationMs > 0 && (_jsxs(Text, { as: "span", sx: { color: 'fg.subtle' }, children: [' ', "(", formatDuration(durationMs), ")"] }))] }))] }), _jsx(ReactECharts, { echarts: echarts, option: option, style: { width: '100%', height: `${height}px` }, notMerge: true })] }, mtype));
|
|
366
|
+
})] }));
|
|
284
367
|
};
|
|
285
368
|
export default OtelMetricsChart;
|
|
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
* @module otel/OtelSpanDetail
|
|
13
13
|
*/
|
|
14
14
|
import { useMemo, useState } from 'react';
|
|
15
|
-
import { Box } from '@datalayer/primer-addons';
|
|
15
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
16
16
|
import { Text, IconButton, UnderlineNav, CounterLabel, Label, } from '@primer/react';
|
|
17
17
|
import { XIcon, ChevronDownIcon, ChevronRightIcon, } from '@primer/octicons-react';
|
|
18
18
|
import { formatDuration, buildSpanTree } from '../utils.js';
|
|
@@ -39,7 +39,7 @@ const CollapsibleSection = ({ title, data, defaultOpen = false }) => {
|
|
|
39
39
|
const [open, setOpen] = useState(defaultOpen);
|
|
40
40
|
if (!data || Object.keys(data).length === 0)
|
|
41
41
|
return null;
|
|
42
|
-
return (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, cursor: "pointer", py: 1, userSelect: "none", onClick: () => setOpen(!open), children: [open ? _jsx(ChevronDownIcon, { size: 16 }) : _jsx(ChevronRightIcon, { size: 16 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold' }, children: title }), _jsx(CounterLabel, { children: Object.keys(data).length })] }), open && (_jsx(
|
|
42
|
+
return (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, cursor: "pointer", py: 1, userSelect: "none", onClick: () => setOpen(!open), children: [open ? _jsx(ChevronDownIcon, { size: 16 }) : _jsx(ChevronRightIcon, { size: 16 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold' }, children: title }), _jsx(CounterLabel, { children: Object.keys(data).length })] }), open && (_jsx(Card, { border: true, shadow: "none", variant: "subtle", display: "block", p: 2, mt: 1, overflowX: "auto", children: Object.entries(data).map(([key, val]) => (_jsx(AttributeRow, { attrKey: key, value: val, depth: 0 }, key))) }))] }));
|
|
43
43
|
};
|
|
44
44
|
/**
|
|
45
45
|
* A value as the detail unfolds it: JSON text of an object or an array (the
|
|
@@ -107,7 +107,7 @@ export const OtelSpanDetail = ({ span, traceSpans, onClose, }) => {
|
|
|
107
107
|
}
|
|
108
108
|
// Build tree from traceSpans for tree tab
|
|
109
109
|
const tree = traceSpans && traceSpans.length > 0 ? buildSpanTree(traceSpans) : undefined;
|
|
110
|
-
return (_jsxs(Box, { height: "100%", overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", bg: "canvas.default", display: "flex", flexDirection: "column", children: [_jsxs(Box, { px: 3, py: 2, borderBottom: "1px solid", borderColor: "border.default", display: "flex", justifyContent: "space-between", alignItems: "center", bg: "canvas.subtle", flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: span.span_name }), onClose && (_jsx(IconButton, { icon: XIcon, "aria-label": "Close detail panel", variant: "invisible", size: "small", onClick: onClose }))] }), _jsxs(UnderlineNav, { "aria-label": "Span detail tabs", children: [_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'details' ? 'page' : undefined, onClick: () => setActiveTab('details'), children: "Details" }), tree && (_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'tree' ? 'page' : undefined, onClick: () => setActiveTab('tree'), children: "Trace Tree" })), _jsx(UnderlineNav.Item, { "aria-current": activeTab === 'raw' ? 'page' : undefined, onClick: () => setActiveTab('raw'), children: "Raw Data" })] }), _jsxs(Box, { flex: 1, overflow: "auto", children: [activeTab === 'details' && (_jsxs(Box, { p: 3, children: [_jsx(MetadataRow, { label: "span_name", value: span.span_name }), _jsx(MetadataRow, { label: "service_name", value: span.service_name }), _jsx(MetadataRow, { label: "otel_scope_name", value: span.otel_scope_name }), _jsx(MetadataRow, { label: "kind", value: span.kind }), _jsx(MetadataRow, { label: "trace_id", value: span.trace_id, mono: true }), _jsx(MetadataRow, { label: "span_id", value: span.span_id, mono: true }), _jsx(MetadataRow, { label: "parent_span_id", value: span.parent_span_id, mono: true }), _jsx(MetadataRow, { label: "duration", value: formatDuration(span.duration_ms) }), _jsx(MetadataRow, { label: "start_time", value: span.start_time, mono: true }), _jsx(MetadataRow, { label: "end_time", value: span.end_time, mono: true }), _jsx(MetadataRow, { label: "status_code", value: span.status_code }), _jsx(MetadataRow, { label: "status_message", value: span.status_message }), _jsx(CollapsibleSection, { title: "Arguments", data: genAiAttrs, defaultOpen: Object.keys(genAiAttrs).length > 0 }), _jsx(CollapsibleSection, { title: "Attributes", data: otherAttrs }), span.events && span.events.length > 0 && (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: "Events" }), _jsx(CounterLabel, { children: span.events.length })] }), span.events.map((ev, idx) => (_jsxs(
|
|
110
|
+
return (_jsxs(Box, { height: "100%", overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", bg: "canvas.default", display: "flex", flexDirection: "column", children: [_jsxs(Box, { px: 3, py: 2, borderBottom: "1px solid", borderColor: "border.default", display: "flex", justifyContent: "space-between", alignItems: "center", bg: "canvas.subtle", flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: span.span_name }), onClose && (_jsx(IconButton, { icon: XIcon, "aria-label": "Close detail panel", variant: "invisible", size: "small", onClick: onClose }))] }), _jsxs(UnderlineNav, { "aria-label": "Span detail tabs", children: [_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'details' ? 'page' : undefined, onClick: () => setActiveTab('details'), children: "Details" }), tree && (_jsx(UnderlineNav.Item, { "aria-current": activeTab === 'tree' ? 'page' : undefined, onClick: () => setActiveTab('tree'), children: "Trace Tree" })), _jsx(UnderlineNav.Item, { "aria-current": activeTab === 'raw' ? 'page' : undefined, onClick: () => setActiveTab('raw'), children: "Raw Data" })] }), _jsxs(Box, { flex: 1, overflow: "auto", children: [activeTab === 'details' && (_jsxs(Box, { p: 3, children: [_jsx(MetadataRow, { label: "span_name", value: span.span_name }), _jsx(MetadataRow, { label: "service_name", value: span.service_name }), _jsx(MetadataRow, { label: "otel_scope_name", value: span.otel_scope_name }), _jsx(MetadataRow, { label: "kind", value: span.kind }), _jsx(MetadataRow, { label: "trace_id", value: span.trace_id, mono: true }), _jsx(MetadataRow, { label: "span_id", value: span.span_id, mono: true }), _jsx(MetadataRow, { label: "parent_span_id", value: span.parent_span_id, mono: true }), _jsx(MetadataRow, { label: "duration", value: formatDuration(span.duration_ms) }), _jsx(MetadataRow, { label: "start_time", value: span.start_time, mono: true }), _jsx(MetadataRow, { label: "end_time", value: span.end_time, mono: true }), _jsx(MetadataRow, { label: "status_code", value: span.status_code }), _jsx(MetadataRow, { label: "status_message", value: span.status_message }), _jsx(CollapsibleSection, { title: "Arguments", data: genAiAttrs, defaultOpen: Object.keys(genAiAttrs).length > 0 }), _jsx(CollapsibleSection, { title: "Attributes", data: otherAttrs }), span.events && span.events.length > 0 && (_jsxs(Box, { mt: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: "Events" }), _jsx(CounterLabel, { children: span.events.length })] }), span.events.map((ev, idx) => (_jsxs(Card, { border: true, shadow: "none", variant: "subtle", display: "block", p: 2, mt: 2, children: [_jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Label, { children: ev.name }), _jsx(Text, { sx: {
|
|
111
111
|
fontSize: 0,
|
|
112
112
|
color: 'fg.muted',
|
|
113
113
|
fontFamily: 'mono',
|
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
* Available tables: `spans`, `metrics`, `logs`.
|
|
15
15
|
*/
|
|
16
16
|
import { useState, useCallback } from 'react';
|
|
17
|
-
import { Box } from '@datalayer/primer-addons';
|
|
17
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
18
18
|
import { ActionList, ActionMenu, Button, Text, Textarea, Spinner, } from '@primer/react';
|
|
19
19
|
import { HistoryIcon, TableIcon, XCircleIcon, DownloadIcon, } from '@primer/octicons-react';
|
|
20
20
|
import { useOtelQuery } from '../hooks/index.js';
|
|
@@ -105,7 +105,7 @@ export const OtelSqlView = ({ token, baseUrl = '', }) => {
|
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
108
|
-
return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, height: "100%", gap: 3, p: 4, bg: "canvas.default", color: "fg.default", overflow: "auto", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", children: "Preset queries" }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { children: PRESET_GROUPS.map(group => (_jsx(ActionList.Group, { title: group, children: PRESETS.filter(p => p.group === group).map(p => (_jsx(ActionList.Item, { onSelect: () => setSql(p.sql), children: p.label }, p.label))) }, group))) }) })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["\u2318 Enter to run \u00B7 Tables:", ' ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "spans" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "metrics" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "logs" })] })] }), _jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsx(Textarea, { "aria-label": "SQL query", value: sql, onChange: e => setSql(e.target.value), onKeyDown: handleKeyDown, placeholder: "SELECT \u2026 FROM spans LIMIT 20", rows: 5, resize: "vertical", sx: { fontFamily: 'mono', fontSize: 1, width: '100%' } }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Button, { variant: "primary", onClick: handleRun, disabled: loading, size: "small", children: loading ? 'Running…' : 'Run' }), loading && _jsx(Spinner, { size: "small" })] })] }), error && (_jsx(
|
|
108
|
+
return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, height: "100%", gap: 3, p: 4, bg: "canvas.default", color: "fg.default", overflow: "auto", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", children: "Preset queries" }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { children: PRESET_GROUPS.map(group => (_jsx(ActionList.Group, { title: group, children: PRESETS.filter(p => p.group === group).map(p => (_jsx(ActionList.Item, { onSelect: () => setSql(p.sql), children: p.label }, p.label))) }, group))) }) })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["\u2318 Enter to run \u00B7 Tables:", ' ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "spans" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "metrics" }), ', ', _jsx(Box, { as: "code", fontFamily: "mono", fontSize: 0, children: "logs" })] })] }), _jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsx(Textarea, { "aria-label": "SQL query", value: sql, onChange: e => setSql(e.target.value), onKeyDown: handleKeyDown, placeholder: "SELECT \u2026 FROM spans LIMIT 20", rows: 5, resize: "vertical", sx: { fontFamily: 'mono', fontSize: 1, width: '100%' } }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Button, { variant: "primary", onClick: handleRun, disabled: loading, size: "small", children: loading ? 'Running…' : 'Run' }), loading && _jsx(Spinner, { size: "small" })] })] }), error && (_jsx(Card, { border: true, shadow: "none", display: "block", p: 3, bg: "danger.subtle", borderColor: "danger.muted", children: _jsx(Text, { sx: { color: 'danger.fg', fontFamily: 'mono', fontSize: 1 }, children: error }) })), !error && rows.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, mb: 1, children: [_jsx(TableIcon, { size: 14 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'fg.default' }, children: "Results" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', ml: 1 }, children: ["(", rows.length, " row", rows.length !== 1 ? 's' : '', ")"] }), _jsxs(Box, { as: "button", onClick: clear, title: "Clear results", ml: "auto", display: "flex", alignItems: "center", gap: 1, px: 2, py: "2px", border: "none", borderRadius: 2, bg: "transparent", color: "fg.muted", cursor: "pointer", fontSize: 0, hover: { color: 'danger.fg', bg: 'danger.subtle' }, children: [_jsx(XCircleIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "Clear" })] }), _jsxs(Box, { as: "button", onClick: () => {
|
|
109
109
|
const header = columns.join(',');
|
|
110
110
|
const body = rows
|
|
111
111
|
.map(row => columns
|
|
@@ -128,12 +128,12 @@ export const OtelSqlView = ({ token, baseUrl = '', }) => {
|
|
|
128
128
|
a.download = 'otel-query-result.csv';
|
|
129
129
|
a.click();
|
|
130
130
|
URL.revokeObjectURL(url);
|
|
131
|
-
}, title: "Download as CSV", display: "flex", alignItems: "center", gap: 1, px: 2, py: "2px", border: "none", borderRadius: 2, bg: "transparent", color: "fg.muted", cursor: "pointer", fontSize: 0, hover: { color: 'accent.fg', bg: 'accent.subtle' }, children: [_jsx(DownloadIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "CSV" })] })] }), _jsx(
|
|
131
|
+
}, title: "Download as CSV", display: "flex", alignItems: "center", gap: 1, px: 2, py: "2px", border: "none", borderRadius: 2, bg: "transparent", color: "fg.muted", cursor: "pointer", fontSize: 0, hover: { color: 'accent.fg', bg: 'accent.subtle' }, children: [_jsx(DownloadIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "CSV" })] })] }), _jsx(Card, { border: true, shadow: "none", display: "block", flex: 1, minHeight: 200, overflow: "auto", children: _jsxs(Box, { as: "table", width: "100%", borderCollapse: "collapse", children: [_jsx(Box, { as: "thead", children: _jsx(Box, { as: "tr", bg: "canvas.subtle", children: columns.map(col => (_jsx(Box, { as: "th", px: 3, py: 2, textAlign: "left", fontWeight: "bold", fontSize: 0, color: "fg.muted", borderBottom: "1px solid", borderColor: "border.default", whiteSpace: "nowrap", children: col }, col))) }) }), _jsx(Box, { as: "tbody", children: rows.map((row, ri) => (_jsx(Box, { as: "tr", hover: { bg: 'canvas.subtle' }, sx: {
|
|
132
132
|
'&:not(:last-child) td': {
|
|
133
133
|
borderBottom: '1px solid',
|
|
134
134
|
borderColor: 'border.muted',
|
|
135
135
|
},
|
|
136
|
-
}, children: columns.map(col => (_jsx(Box, { as: "td", px: 3, py: 2, fontSize: 0, fontFamily: "mono", maxWidth: "300px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: String(row[col] ?? '') }, col))) }, ri))) })] }) })] })), !error && !loading && rows.length === 0 && (_jsx(Box, { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "fg.muted", children: _jsx(Text, { sx: { fontSize: 1 }, children: "Run a query to see results." }) })), history.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, mb: 1, children: [_jsx(HistoryIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0, fontWeight: 'bold', color: 'fg.muted' }, children: "History" })] }), _jsx(
|
|
136
|
+
}, children: columns.map(col => (_jsx(Box, { as: "td", px: 3, py: 2, fontSize: 0, fontFamily: "mono", maxWidth: "300px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: String(row[col] ?? '') }, col))) }, ri))) })] }) })] })), !error && !loading && rows.length === 0 && (_jsx(Box, { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "fg.muted", children: _jsx(Text, { sx: { fontSize: 1 }, children: "Run a query to see results." }) })), history.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 1, mb: 1, children: [_jsx(HistoryIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0, fontWeight: 'bold', color: 'fg.muted' }, children: "History" })] }), _jsx(Card, { border: true, shadow: "none", display: "flex", flexDirection: "column", overflow: "hidden", children: history.map((entry, i) => (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, px: 3, py: "6px", cursor: "pointer", bg: i % 2 === 0 ? 'canvas.default' : 'canvas.subtle', borderTop: i > 0 ? '1px solid' : 'none', borderColor: "border.muted", hover: { bg: 'accent.subtle' }, onClick: () => setSql(entry), children: [_jsx(Text, { sx: {
|
|
137
137
|
fontFamily: 'mono',
|
|
138
138
|
fontSize: 0,
|
|
139
139
|
color: 'fg.default',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box } from '@datalayer/primer-addons';
|
|
2
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
3
3
|
import { Button, Spinner, Text } from '@primer/react';
|
|
4
4
|
import { SyncIcon } from '@primer/octicons-react';
|
|
5
5
|
import { useOtelSystem } from '../hooks/index.js';
|
|
@@ -41,5 +41,5 @@ const SystemViewContent = ({ data }) => {
|
|
|
41
41
|
};
|
|
42
42
|
export const OtelSystemView = ({ baseUrl = '', token, }) => {
|
|
43
43
|
const { data, loading, error, refresh } = useOtelSystem({ baseUrl, token });
|
|
44
|
-
return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, overflow: "auto", p: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", mb: 3, flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: "System Statistics" }), _jsx(Button, { size: "small", variant: "invisible", onClick: refresh, disabled: loading, leadingVisual: SyncIcon, children: "Refresh" })] }), loading && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Loading\u2026" })] })), !loading && error && (_jsx(
|
|
44
|
+
return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, overflow: "auto", p: 3, children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", mb: 3, flexShrink: 0, children: [_jsx(Text, { sx: { fontSize: 2, fontWeight: 'bold' }, children: "System Statistics" }), _jsx(Button, { size: "small", variant: "invisible", onClick: refresh, disabled: loading, leadingVisual: SyncIcon, children: "Refresh" })] }), loading && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Loading\u2026" })] })), !loading && error && (_jsx(Card, { border: true, shadow: "none", display: "block", bg: "danger.subtle", borderColor: "danger.muted", p: 3, children: _jsx(Text, { sx: { color: 'danger.fg', fontSize: 1 }, children: error }) })), !loading && !error && data && _jsx(SystemViewContent, { data: data }), !loading && !error && !data && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "No data available." }))] }));
|
|
45
45
|
};
|
|
@@ -17,7 +17,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
17
17
|
* @module otel/OtelTimelineRangeSlider
|
|
18
18
|
*/
|
|
19
19
|
import { useCallback, useRef, useState, useMemo } from 'react';
|
|
20
|
-
import { Box } from '@datalayer/primer-addons';
|
|
20
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
21
21
|
import { Text } from '@primer/react';
|
|
22
22
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
23
23
|
function clamp(v, min, max) {
|
|
@@ -180,7 +180,7 @@ export const OtelTimelineRangeSlider = ({ timelineStart, timelineEnd, selectedSt
|
|
|
180
180
|
const barWidth = 100 / (histogram.length || 1);
|
|
181
181
|
const barH = (h.count / histMax) * 100;
|
|
182
182
|
return (_jsx(Box, { position: "absolute", bottom: 0, left: `${pct}%`, width: `${barWidth}%`, height: `${barH}%`, bg: "accent.fg", opacity: 0.35, borderRadius: "1px 1px 0 0", pointerEvents: "none", zIndex: 4 }, i));
|
|
183
|
-
}), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'start'), position: "absolute", top: 0, left: `${startPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'end'), position: "absolute", top: 0, left: `${endPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), hoverPct !== null && !isDragging && (_jsxs(_Fragment, { children: [_jsx(Box, { position: "absolute", top: 0, left: `${hoverPct}%`, width: "1px", height: RAIL_HEIGHT, borderLeft: "1px dashed", borderColor: "fg.default", opacity: 0.55, pointerEvents: "none", zIndex: 5 }), _jsx(
|
|
183
|
+
}), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'start'), position: "absolute", top: 0, left: `${startPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'end'), position: "absolute", top: 0, left: `${endPct}%`, width: HANDLE_WIDTH, height: HANDLE_HEIGHT, ml: `-${HANDLE_WIDTH / 2}px`, bg: "accent.fg", borderRadius: 1, cursor: "ew-resize", zIndex: 3, display: "flex", alignItems: "center", justifyContent: "center", transition: isDragging ? 'none' : 'left 0.05s ease', boxShadow: "shadow.medium", hover: { bg: 'accent.emphasis' }, children: _jsx(Box, { width: "2px", height: "40%", bg: "fg.onEmphasis", borderRadius: 1 }) }), hoverPct !== null && !isDragging && (_jsxs(_Fragment, { children: [_jsx(Box, { position: "absolute", top: 0, left: `${hoverPct}%`, width: "1px", height: RAIL_HEIGHT, borderLeft: "1px dashed", borderColor: "fg.default", opacity: 0.55, pointerEvents: "none", zIndex: 5 }), _jsx(Card, { border: true, shadow: "none", display: "block", position: "absolute", top: -36, left: `${hoverPct}%`, transform: "translateX(-50%)", bg: "canvas.overlay", px: 2, py: "2px", pointerEvents: "none", zIndex: 10, boxShadow: "shadow.medium", whiteSpace: "nowrap", children: _jsx(Text, { sx: {
|
|
184
184
|
fontSize: '11px',
|
|
185
185
|
color: 'fg.default',
|
|
186
186
|
fontFamily: 'mono',
|
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
* @module otel/OtelTracesList
|
|
15
15
|
*/
|
|
16
16
|
import { useMemo, useCallback, useState } from 'react';
|
|
17
|
-
import { Box } from '@datalayer/primer-addons';
|
|
17
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
18
18
|
import { Text, Label, Spinner } from '@primer/react';
|
|
19
19
|
import { Blankslate } from '@primer/react/experimental';
|
|
20
20
|
import { TelescopeIcon, ChevronRightIcon, ChevronDownIcon, } from '@primer/octicons-react';
|
|
@@ -123,7 +123,7 @@ export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, e
|
|
|
123
123
|
const usage = getTokenUsage(span);
|
|
124
124
|
if (!usage)
|
|
125
125
|
return null;
|
|
126
|
-
return (_jsxs(
|
|
126
|
+
return (_jsxs(Card, { border: true, shadow: "none", display: "inline-flex", alignItems: "center", gap: "2px", px: 1, fontSize: "10px", fontFamily: "mono", color: "fg.muted", flexShrink: 0, lineHeight: "16px", children: [usage.input != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
|
|
127
127
|
})(), description && (_jsx(Text, { sx: {
|
|
128
128
|
fontSize: 1,
|
|
129
129
|
color: 'fg.muted',
|
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
6
|
import { useStore } from 'zustand';
|
|
7
7
|
import { registerSessionState } from '../sessionEnd.js';
|
|
8
|
+
/**
|
|
9
|
+
* Whether an update is about the entity the layout holds. An update naming
|
|
10
|
+
* another space or organization (by `uid`, else `id`) replaces the one held:
|
|
11
|
+
* merged over it, the fields the new one does not carry (an organization's
|
|
12
|
+
* handle, a team) stayed from the one left. An update naming none, or one
|
|
13
|
+
* naming the same, is merged onto what is held.
|
|
14
|
+
*/
|
|
15
|
+
const entityUid = (entity) => {
|
|
16
|
+
const record = entity;
|
|
17
|
+
const uid = record?.uid ?? record?.id;
|
|
18
|
+
return typeof uid === 'string' && uid.trim() ? uid.trim() : undefined;
|
|
19
|
+
};
|
|
20
|
+
const sameEntity = (held, update) => {
|
|
21
|
+
const heldUid = entityUid(held);
|
|
22
|
+
const updateUid = entityUid(update);
|
|
23
|
+
return !heldUid || !updateUid || heldUid === updateUid;
|
|
24
|
+
};
|
|
8
25
|
/**
|
|
9
26
|
* Name of the cookie holding the space last selected by the user.
|
|
10
27
|
*
|
|
@@ -25,7 +42,9 @@ const readSpaceContextCookie = () => {
|
|
|
25
42
|
}
|
|
26
43
|
try {
|
|
27
44
|
const parsed = JSON.parse(decodeURIComponent(match[1]));
|
|
28
|
-
if (!parsed ||
|
|
45
|
+
if (!parsed ||
|
|
46
|
+
typeof parsed !== 'object' ||
|
|
47
|
+
typeof parsed.id !== 'string') {
|
|
29
48
|
return undefined;
|
|
30
49
|
}
|
|
31
50
|
return {
|
|
@@ -102,7 +121,9 @@ export const layoutStore = createStore((set, get) => ({
|
|
|
102
121
|
return {
|
|
103
122
|
organization: organization
|
|
104
123
|
? {
|
|
105
|
-
...state.organization,
|
|
124
|
+
...(sameEntity(state.organization, organization)
|
|
125
|
+
? state.organization
|
|
126
|
+
: undefined),
|
|
106
127
|
...organization,
|
|
107
128
|
}
|
|
108
129
|
: undefined,
|
|
@@ -121,7 +142,7 @@ export const layoutStore = createStore((set, get) => ({
|
|
|
121
142
|
updateLayoutSpace: (space) => set((state) => {
|
|
122
143
|
const next = space
|
|
123
144
|
? {
|
|
124
|
-
...state.space,
|
|
145
|
+
...(sameEntity(state.space, space) ? state.space : undefined),
|
|
125
146
|
...space,
|
|
126
147
|
}
|
|
127
148
|
: undefined;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `datalayer-research` organization, at `/research` (the public papers, read
|
|
16
16
|
* signed in, at `/research/papers/…` beside them).
|
|
17
17
|
*/
|
|
18
|
-
export type NavigationView = 'studio' | 'home' | 'agentify' | 'research' | 'admin' | 'apps';
|
|
18
|
+
export type NavigationView = 'studio' | 'home' | 'spaces' | 'agentify' | 'research' | 'admin' | 'apps';
|
|
19
19
|
/** The shells, and where each one opens when it has not been visited. */
|
|
20
20
|
export declare const NAVIGATION_VIEW_HOMES: Record<NavigationView, string>;
|
|
21
21
|
/** Well-known view identifiers for {@link NavigationState.tabsByView}. */
|
|
@@ -77,13 +77,21 @@ export declare function isRememberableRoute(route: string): boolean;
|
|
|
77
77
|
*/
|
|
78
78
|
export declare function isHostedAppRoute(route: string): boolean;
|
|
79
79
|
/**
|
|
80
|
-
* Whether a page belongs to no shell: the settings, the documentation
|
|
81
|
-
* the
|
|
80
|
+
* Whether a page belongs to no shell: the settings, the documentation, the
|
|
81
|
+
* presentations and the agentspecs catalogue (since 8 November 2026: reached
|
|
82
|
+
* from an agent's picker on /agents/new, its env var's page swapped the
|
|
83
|
+
* Sessions sidebar for the Studio's).
|
|
82
84
|
*
|
|
83
85
|
* They are read from whichever shell the reader is in — its sidebar stays
|
|
84
86
|
* beside them and the header's selector does not move — so they are never a
|
|
85
87
|
* shell's own page: opening one does not change the shell, and no shell
|
|
86
88
|
* returns to one.
|
|
89
|
+
*
|
|
90
|
+
* The presentations are `/decks/<collection>/<slug>` and their list,
|
|
91
|
+
* `/presentations`. Bare `/decks` is not one of them: it is the selected
|
|
92
|
+
* space's decks table, a page of the Spaces shell (the user, 12 November
|
|
93
|
+
* 2026). Neutral, it took the current shell whenever one was given, and the
|
|
94
|
+
* Sessions sidebar stayed beside it.
|
|
87
95
|
*/
|
|
88
96
|
export declare function isShellNeutralRoute(route: string): boolean;
|
|
89
97
|
/**
|