@datalayer/core 1.0.2 → 1.0.11
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/README.md +1 -1
- package/lib/api/constants.d.ts +6 -0
- package/lib/api/constants.js +6 -0
- package/lib/api/index.d.ts +1 -0
- package/lib/api/index.js +1 -0
- package/lib/api/otel/index.d.ts +12 -0
- package/lib/api/otel/index.js +16 -0
- package/lib/api/otel/logs.d.ts +19 -0
- package/lib/api/otel/logs.js +43 -0
- package/lib/api/otel/metrics.d.ts +31 -0
- package/lib/api/otel/metrics.js +65 -0
- package/lib/api/otel/query.d.ts +16 -0
- package/lib/api/otel/query.js +37 -0
- package/lib/api/otel/services.d.ts +39 -0
- package/lib/api/otel/services.js +81 -0
- package/lib/api/otel/traces.d.ts +24 -0
- package/lib/api/otel/traces.js +53 -0
- package/lib/api/otel/types.d.ts +112 -0
- package/lib/api/otel/types.js +5 -0
- package/lib/api/runtimes/checkpoints.d.ts +122 -0
- package/lib/api/runtimes/checkpoints.js +118 -0
- package/lib/api/runtimes/index.d.ts +1 -0
- package/lib/api/runtimes/index.js +1 -0
- package/lib/api/runtimes/runtimes.d.ts +84 -0
- package/lib/api/runtimes/runtimes.js +50 -0
- package/lib/components/auth/Login.js +1 -1
- package/lib/components/display/BusyDots.d.ts +9 -0
- package/lib/components/display/BusyDots.js +31 -0
- package/lib/components/display/LiveRelativeTime.d.ts +10 -0
- package/lib/components/display/LiveRelativeTime.js +21 -0
- package/lib/components/display/index.d.ts +2 -0
- package/lib/components/display/index.js +2 -0
- package/lib/components/flashes/FlashSurveys.js +1 -1
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/navbar/SubdomainNavBar.js +1 -1
- package/lib/components/progress/ConsumptionBar.js +6 -7
- package/lib/components/progress/CreditsIndicator.js +2 -2
- package/lib/components/progress/consumption.d.ts +12 -0
- package/lib/components/progress/consumption.js +31 -0
- package/lib/components/progress/index.d.ts +1 -0
- package/lib/components/progress/index.js +1 -0
- package/lib/components/sparklines/Sparklines.d.ts +16 -0
- package/lib/components/sparklines/Sparklines.js +65 -0
- package/lib/components/sparklines/SparklinesLine.d.ts +8 -0
- package/lib/components/sparklines/SparklinesLine.js +37 -0
- package/lib/components/sparklines/dataProcessing.d.ts +25 -0
- package/lib/components/sparklines/dataProcessing.js +35 -0
- package/lib/components/sparklines/index.d.ts +4 -0
- package/lib/components/sparklines/index.js +7 -0
- package/lib/components/sparklines/types.d.ts +36 -0
- package/lib/components/sparklines/types.js +5 -0
- package/lib/components/storage/ContentsBrowser.js +17 -1
- package/lib/components/subnav/SubNav.js +1 -1
- package/lib/config/Configuration.d.ts +4 -0
- package/lib/hooks/useCache.d.ts +6 -63
- package/lib/hooks/useCache.js +35 -205
- package/lib/hooks/useProjects.d.ts +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +4 -0
- package/lib/models/ItemDTO.js +1 -1
- package/lib/models/RolesPlatform.js +2 -2
- package/lib/models/User.d.ts +2 -0
- package/lib/models/User.js +4 -1
- package/lib/otel/client/OtelClient.d.ts +93 -0
- package/lib/otel/client/OtelClient.js +232 -0
- package/lib/otel/client/index.d.ts +2 -0
- package/lib/otel/client/index.js +5 -0
- package/lib/otel/hooks/index.d.ts +186 -0
- package/lib/otel/hooks/index.js +532 -0
- package/lib/otel/index.d.ts +34 -0
- package/lib/otel/index.js +23 -0
- package/lib/otel/types.d.ts +190 -0
- package/lib/otel/types.js +5 -0
- package/lib/otel/utils.d.ts +33 -0
- package/lib/otel/utils.js +181 -0
- package/lib/otel/views/OtelLive.d.ts +12 -0
- package/lib/otel/views/OtelLive.js +372 -0
- package/lib/otel/views/OtelLogsList.d.ts +11 -0
- package/lib/otel/views/OtelLogsList.js +137 -0
- package/lib/otel/views/OtelMetricsChart.d.ts +22 -0
- package/lib/otel/views/OtelMetricsChart.js +300 -0
- package/lib/otel/views/OtelMetricsList.d.ts +15 -0
- package/lib/otel/views/OtelMetricsList.js +213 -0
- package/lib/otel/views/OtelSearchBar.d.ts +11 -0
- package/lib/otel/views/OtelSearchBar.js +22 -0
- package/lib/otel/views/OtelSpanDetail.d.ts +11 -0
- package/lib/otel/views/OtelSpanDetail.js +172 -0
- package/lib/otel/views/OtelSpanTree.d.ts +11 -0
- package/lib/otel/views/OtelSpanTree.js +176 -0
- package/lib/otel/views/OtelSqlView.d.ts +16 -0
- package/lib/otel/views/OtelSqlView.js +239 -0
- package/lib/otel/views/OtelSystemView.d.ts +15 -0
- package/lib/otel/views/OtelSystemView.js +75 -0
- package/lib/otel/views/OtelTimeline.d.ts +11 -0
- package/lib/otel/views/OtelTimeline.js +101 -0
- package/lib/otel/views/OtelTimelineRangeSlider.d.ts +16 -0
- package/lib/otel/views/OtelTimelineRangeSlider.js +338 -0
- package/lib/otel/views/OtelTracesList.d.ts +13 -0
- package/lib/otel/views/OtelTracesList.js +199 -0
- package/lib/otel/views/index.d.ts +20 -0
- package/lib/otel/views/index.js +21 -0
- package/lib/state/storage/IAMStorage.d.ts +2 -1
- package/lib/state/substates/CoreState.js +7 -6
- package/lib/utils/Date.d.ts +6 -0
- package/lib/utils/Date.js +37 -0
- package/lib/utils/Jwt.d.ts +42 -0
- package/lib/utils/Jwt.js +44 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/views/iam/SignInSimple.d.ts +43 -0
- package/lib/views/iam/SignInSimple.js +113 -0
- package/lib/views/iam/index.d.ts +2 -0
- package/lib/views/iam/index.js +5 -0
- package/lib/views/iam-tokens/IAMTokenEdit.d.ts +5 -1
- package/lib/views/iam-tokens/IAMTokenEdit.js +54 -5
- package/lib/views/iam-tokens/IAMTokenNew.js +2 -2
- package/lib/views/iam-tokens/IAMTokens.d.ts +4 -2
- package/lib/views/iam-tokens/IAMTokens.js +68 -36
- package/lib/views/iam-tokens/Tokens.js +63 -31
- package/lib/views/index.d.ts +3 -1
- package/lib/views/index.js +3 -1
- package/lib/views/otel/DashboardView.d.ts +16 -0
- package/lib/views/otel/DashboardView.js +4 -0
- package/lib/views/otel/LogsView.d.ts +12 -0
- package/lib/views/otel/LogsView.js +4 -0
- package/lib/views/otel/MetricsView.d.ts +12 -0
- package/lib/views/otel/MetricsView.js +4 -0
- package/lib/views/otel/OtelHeader.d.ts +33 -0
- package/lib/views/otel/OtelHeader.js +105 -0
- package/lib/views/otel/SqlView.d.ts +9 -0
- package/lib/views/otel/SqlView.js +4 -0
- package/lib/views/otel/SystemView.d.ts +9 -0
- package/lib/views/otel/SystemView.js +4 -0
- package/lib/views/otel/TracesView.d.ts +12 -0
- package/lib/views/otel/TracesView.js +4 -0
- package/lib/views/otel/index.d.ts +16 -0
- package/lib/views/otel/index.js +12 -0
- package/lib/views/otel/simpleAuthStore.d.ts +21 -0
- package/lib/views/otel/simpleAuthStore.js +22 -0
- package/lib/views/profile/UserBadge.d.ts +20 -0
- package/lib/views/profile/UserBadge.js +101 -0
- package/lib/views/profile/index.d.ts +2 -0
- package/lib/views/profile/index.js +5 -0
- package/package.json +3 -4
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
* OtelSpanDetail – Detail panel for a selected span, with metadata,
|
|
8
|
+
* collapsible attributes, gen_ai arguments, events, and links.
|
|
9
|
+
*
|
|
10
|
+
* Uses Primer React components for consistent theming.
|
|
11
|
+
*
|
|
12
|
+
* @module otel/OtelSpanDetail
|
|
13
|
+
*/
|
|
14
|
+
import { useState } from 'react';
|
|
15
|
+
import { Box, Text, IconButton, UnderlineNav, CounterLabel, Label, } from '@primer/react';
|
|
16
|
+
import { XIcon, ChevronDownIcon, ChevronRightIcon, } from '@primer/octicons-react';
|
|
17
|
+
import { formatDuration, buildSpanTree } from '../utils';
|
|
18
|
+
import { OtelSpanTree } from './OtelSpanTree';
|
|
19
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
20
|
+
/** Single key–value metadata row. */
|
|
21
|
+
const MetadataRow = ({ label, value, mono = false }) => {
|
|
22
|
+
if (value === undefined || value === null || value === '')
|
|
23
|
+
return null;
|
|
24
|
+
return (_jsxs(Box, { sx: {
|
|
25
|
+
display: 'flex',
|
|
26
|
+
py: 1,
|
|
27
|
+
borderBottom: '1px solid',
|
|
28
|
+
borderColor: 'border.muted',
|
|
29
|
+
gap: 2,
|
|
30
|
+
}, children: [_jsx(Text, { sx: {
|
|
31
|
+
width: 140,
|
|
32
|
+
minWidth: 140,
|
|
33
|
+
color: 'fg.muted',
|
|
34
|
+
fontSize: 1,
|
|
35
|
+
fontWeight: 'bold',
|
|
36
|
+
}, children: label }), _jsx(Text, { sx: {
|
|
37
|
+
fontSize: 1,
|
|
38
|
+
fontFamily: mono ? 'mono' : 'normal',
|
|
39
|
+
wordBreak: 'break-all',
|
|
40
|
+
}, children: String(value) })] }));
|
|
41
|
+
};
|
|
42
|
+
/** Collapsible section for nested JSON/attribute data with tree rendering. */
|
|
43
|
+
const CollapsibleSection = ({ title, data, defaultOpen = false }) => {
|
|
44
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
45
|
+
if (!data || Object.keys(data).length === 0)
|
|
46
|
+
return null;
|
|
47
|
+
return (_jsxs(Box, { sx: { mt: 3 }, children: [_jsxs(Box, { sx: {
|
|
48
|
+
display: 'flex',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
gap: 1,
|
|
51
|
+
cursor: 'pointer',
|
|
52
|
+
py: 1,
|
|
53
|
+
userSelect: 'none',
|
|
54
|
+
}, 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(Box, { sx: {
|
|
55
|
+
bg: 'canvas.subtle',
|
|
56
|
+
borderRadius: 2,
|
|
57
|
+
border: '1px solid',
|
|
58
|
+
borderColor: 'border.default',
|
|
59
|
+
p: 2,
|
|
60
|
+
mt: 1,
|
|
61
|
+
overflowX: 'auto',
|
|
62
|
+
}, children: Object.entries(data).map(([key, val]) => (_jsx(AttributeRow, { attrKey: key, value: val, depth: 0 }, key))) }))] }));
|
|
63
|
+
};
|
|
64
|
+
/** Recursive attribute row – supports nested objects and arrays. */
|
|
65
|
+
const AttributeRow = ({ attrKey, value, depth }) => {
|
|
66
|
+
const [open, setOpen] = useState(depth < 1);
|
|
67
|
+
const isObject = typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
68
|
+
const isArray = Array.isArray(value);
|
|
69
|
+
const isNested = isObject || isArray;
|
|
70
|
+
return (_jsxs(Box, { sx: {
|
|
71
|
+
borderBottom: depth === 0 ? '1px solid' : 'none',
|
|
72
|
+
borderColor: 'border.muted',
|
|
73
|
+
pb: depth === 0 ? 1 : 0,
|
|
74
|
+
mb: depth === 0 ? 1 : 0,
|
|
75
|
+
}, children: [_jsxs(Box, { sx: {
|
|
76
|
+
display: 'flex',
|
|
77
|
+
gap: 2,
|
|
78
|
+
py: 1,
|
|
79
|
+
pl: depth * 16 + 'px',
|
|
80
|
+
alignItems: 'flex-start',
|
|
81
|
+
}, children: [isNested ? (_jsx(Box, { sx: {
|
|
82
|
+
cursor: 'pointer',
|
|
83
|
+
color: 'fg.muted',
|
|
84
|
+
userSelect: 'none',
|
|
85
|
+
width: 16,
|
|
86
|
+
flexShrink: 0,
|
|
87
|
+
}, onClick: () => setOpen(!open), children: open ? (_jsx(ChevronDownIcon, { size: 12 })) : (_jsx(ChevronRightIcon, { size: 12 })) })) : (_jsx(Box, { sx: { width: 16, flexShrink: 0 } })), _jsx(Text, { sx: {
|
|
88
|
+
color: 'accent.fg',
|
|
89
|
+
fontSize: 1,
|
|
90
|
+
fontFamily: 'mono',
|
|
91
|
+
minWidth: 180,
|
|
92
|
+
wordBreak: 'break-all',
|
|
93
|
+
flexShrink: 0,
|
|
94
|
+
}, children: attrKey }), !isNested && (_jsx(Text, { sx: {
|
|
95
|
+
fontSize: 1,
|
|
96
|
+
fontFamily: 'mono',
|
|
97
|
+
whiteSpace: 'pre-wrap',
|
|
98
|
+
wordBreak: 'break-word',
|
|
99
|
+
color: typeof value === 'string' ? 'accent.emphasis' : 'attention.fg',
|
|
100
|
+
}, children: typeof value === 'string' ? value : JSON.stringify(value) })), isNested && !open && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: isArray
|
|
101
|
+
? `[${value.length} items]`
|
|
102
|
+
: `{${Object.keys(value).length} keys}` }))] }), isNested && open && (_jsx(Box, { children: isArray
|
|
103
|
+
? value.map((item, idx) => (_jsx(AttributeRow, { attrKey: `[${idx}]`, value: item, depth: depth + 1 }, idx)))
|
|
104
|
+
: Object.entries(value).map(([k, v]) => (_jsx(AttributeRow, { attrKey: k, value: v, depth: depth + 1 }, k))) }))] }));
|
|
105
|
+
};
|
|
106
|
+
// ── Main component ──────────────────────────────────────────────────
|
|
107
|
+
export const OtelSpanDetail = ({ span, traceSpans, onClose, }) => {
|
|
108
|
+
const [activeTab, setActiveTab] = useState('details');
|
|
109
|
+
if (!span) {
|
|
110
|
+
return (_jsx(Box, { sx: { p: 5, color: 'fg.muted', textAlign: 'center' }, children: _jsx(Text, { children: "Select a span to view details." }) }));
|
|
111
|
+
}
|
|
112
|
+
// Separate gen_ai attributes from other attributes
|
|
113
|
+
const genAiAttrs = {};
|
|
114
|
+
const otherAttrs = {};
|
|
115
|
+
if (span.attributes) {
|
|
116
|
+
for (const [k, v] of Object.entries(span.attributes)) {
|
|
117
|
+
if (k.startsWith('gen_ai.') || k.startsWith('model_request')) {
|
|
118
|
+
genAiAttrs[k] = v;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
otherAttrs[k] = v;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Build tree from traceSpans for tree tab
|
|
126
|
+
const tree = traceSpans && traceSpans.length > 0 ? buildSpanTree(traceSpans) : undefined;
|
|
127
|
+
return (_jsxs(Box, { sx: {
|
|
128
|
+
height: '100%',
|
|
129
|
+
overflow: 'auto',
|
|
130
|
+
borderLeft: '1px solid',
|
|
131
|
+
borderColor: 'border.default',
|
|
132
|
+
bg: 'canvas.default',
|
|
133
|
+
display: 'flex',
|
|
134
|
+
flexDirection: 'column',
|
|
135
|
+
}, children: [_jsxs(Box, { sx: {
|
|
136
|
+
px: 3,
|
|
137
|
+
py: 2,
|
|
138
|
+
borderBottom: '1px solid',
|
|
139
|
+
borderColor: 'border.default',
|
|
140
|
+
display: 'flex',
|
|
141
|
+
justifyContent: 'space-between',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
bg: 'canvas.subtle',
|
|
144
|
+
flexShrink: 0,
|
|
145
|
+
}, 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, { sx: { flex: 1, overflow: 'auto' }, children: [activeTab === 'details' && (_jsxs(Box, { sx: { 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, { sx: { mt: 3 }, children: [_jsxs(Box, { sx: { 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(Box, { sx: {
|
|
146
|
+
bg: 'canvas.subtle',
|
|
147
|
+
borderRadius: 2,
|
|
148
|
+
border: '1px solid',
|
|
149
|
+
borderColor: 'border.default',
|
|
150
|
+
p: 2,
|
|
151
|
+
mt: 2,
|
|
152
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2 }, children: [_jsx(Label, { children: ev.name }), _jsx(Text, { sx: {
|
|
153
|
+
fontSize: 0,
|
|
154
|
+
color: 'fg.muted',
|
|
155
|
+
fontFamily: 'mono',
|
|
156
|
+
}, children: ev.timestamp })] }), ev.attributes && Object.keys(ev.attributes).length > 0 && (_jsx(Box, { sx: { mt: 1 }, children: Object.entries(ev.attributes).map(([k, v]) => (_jsx(AttributeRow, { attrKey: k, value: v, depth: 0 }, k))) }))] }, idx)))] })), span.links && span.links.length > 0 && (_jsxs(Box, { sx: { mt: 3 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: "Links" }), _jsx(CounterLabel, { children: span.links.length })] }), span.links.map((link, idx) => (_jsxs(Text, { sx: {
|
|
157
|
+
display: 'block',
|
|
158
|
+
fontSize: 1,
|
|
159
|
+
fontFamily: 'mono',
|
|
160
|
+
py: 1,
|
|
161
|
+
borderBottom: '1px solid',
|
|
162
|
+
borderColor: 'border.muted',
|
|
163
|
+
}, children: ["trace: ", link.trace_id, " \u2192 span: ", link.span_id] }, idx)))] }))] })), activeTab === 'tree' && tree && (_jsx(OtelSpanTree, { spans: tree, selectedSpanId: span.span_id, defaultExpandDepth: 4 })), activeTab === 'raw' && (_jsx(Box, { as: "pre", sx: {
|
|
164
|
+
p: 3,
|
|
165
|
+
fontSize: 0,
|
|
166
|
+
fontFamily: 'mono',
|
|
167
|
+
whiteSpace: 'pre-wrap',
|
|
168
|
+
wordBreak: 'break-word',
|
|
169
|
+
m: 0,
|
|
170
|
+
bg: 'canvas.subtle',
|
|
171
|
+
}, children: JSON.stringify(span, null, 2) }))] })] }));
|
|
172
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OtelSpanTree – Collapsible tree view of nested spans within a trace.
|
|
3
|
+
*
|
|
4
|
+
* Renders spans as an indented, expandable tree with duration bars.
|
|
5
|
+
* Uses Primer React components for consistent theming.
|
|
6
|
+
*
|
|
7
|
+
* @module otel/OtelSpanTree
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import type { OtelSpanTreeProps } from '../types';
|
|
11
|
+
export declare const OtelSpanTree: React.FC<OtelSpanTreeProps>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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
|
+
* OtelSpanTree – Collapsible tree view of nested spans within a trace.
|
|
8
|
+
*
|
|
9
|
+
* Renders spans as an indented, expandable tree with duration bars.
|
|
10
|
+
* Uses Primer React components for consistent theming.
|
|
11
|
+
*
|
|
12
|
+
* @module otel/OtelSpanTree
|
|
13
|
+
*/
|
|
14
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
15
|
+
import { Box, Text } from '@primer/react';
|
|
16
|
+
import { ChevronDownIcon, ChevronRightIcon } from '@primer/octicons-react';
|
|
17
|
+
import { formatDuration, serviceColor, toMs } from '../utils';
|
|
18
|
+
const INDENT_PX = 20;
|
|
19
|
+
const SpanNode = ({ span, depth, selectedSpanId, onSelectSpan, expandedIds, toggleExpanded, traceMinTime, traceDuration, }) => {
|
|
20
|
+
const hasChildren = (span.children?.length ?? 0) > 0;
|
|
21
|
+
const isExpanded = expandedIds.has(span.span_id);
|
|
22
|
+
const isSelected = selectedSpanId === span.span_id;
|
|
23
|
+
const color = serviceColor(span.service_name);
|
|
24
|
+
const startPct = ((toMs(span.start_time) - traceMinTime) / traceDuration) * 100;
|
|
25
|
+
const widthPct = Math.max(((span.duration_ms || 0) / traceDuration) * 100, 0.5);
|
|
26
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
27
|
+
display: 'flex',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
height: 28,
|
|
30
|
+
pl: `${depth * INDENT_PX}px`,
|
|
31
|
+
pr: 2,
|
|
32
|
+
cursor: 'pointer',
|
|
33
|
+
bg: isSelected ? 'accent.subtle' : 'canvas.default',
|
|
34
|
+
borderBottom: '1px solid',
|
|
35
|
+
borderColor: 'border.muted',
|
|
36
|
+
':hover': {
|
|
37
|
+
bg: isSelected ? 'accent.subtle' : 'canvas.subtle',
|
|
38
|
+
},
|
|
39
|
+
}, onClick: () => onSelectSpan?.(span), children: [_jsx(Box, { sx: {
|
|
40
|
+
width: 20,
|
|
41
|
+
textAlign: 'center',
|
|
42
|
+
color: 'fg.muted',
|
|
43
|
+
userSelect: 'none',
|
|
44
|
+
flexShrink: 0,
|
|
45
|
+
cursor: hasChildren ? 'pointer' : 'default',
|
|
46
|
+
}, onClick: e => {
|
|
47
|
+
e.stopPropagation();
|
|
48
|
+
if (hasChildren)
|
|
49
|
+
toggleExpanded(span.span_id);
|
|
50
|
+
}, children: hasChildren ? (isExpanded ? (_jsx(ChevronDownIcon, { size: 12 })) : (_jsx(ChevronRightIcon, { size: 12 }))) : (_jsx(Text, { sx: { color: 'fg.subtle', fontSize: 0 }, children: "\u00B7" })) }), _jsx(Box, { sx: {
|
|
51
|
+
width: 8,
|
|
52
|
+
height: 8,
|
|
53
|
+
borderRadius: '50%',
|
|
54
|
+
bg: color,
|
|
55
|
+
flexShrink: 0,
|
|
56
|
+
mr: 1,
|
|
57
|
+
} }), _jsx(Text, { sx: {
|
|
58
|
+
flex: '0 0 200px',
|
|
59
|
+
overflow: 'hidden',
|
|
60
|
+
textOverflow: 'ellipsis',
|
|
61
|
+
whiteSpace: 'nowrap',
|
|
62
|
+
fontSize: 1,
|
|
63
|
+
fontWeight: isSelected ? 'bold' : 'normal',
|
|
64
|
+
}, title: `${span.service_name} / ${span.span_name}`, children: span.span_name }), _jsx(Box, { sx: {
|
|
65
|
+
flex: 1,
|
|
66
|
+
position: 'relative',
|
|
67
|
+
height: 12,
|
|
68
|
+
bg: 'canvas.subtle',
|
|
69
|
+
borderRadius: 1,
|
|
70
|
+
mx: 2,
|
|
71
|
+
overflow: 'hidden',
|
|
72
|
+
}, children: _jsx(Box, { sx: {
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
left: `${startPct}%`,
|
|
75
|
+
width: `${widthPct}%`,
|
|
76
|
+
height: '100%',
|
|
77
|
+
bg: color,
|
|
78
|
+
borderRadius: 1,
|
|
79
|
+
opacity: isSelected ? 1 : 0.7,
|
|
80
|
+
} }) }), _jsx(Text, { sx: {
|
|
81
|
+
minWidth: 60,
|
|
82
|
+
textAlign: 'right',
|
|
83
|
+
fontSize: 0,
|
|
84
|
+
fontFamily: 'mono',
|
|
85
|
+
color: 'fg.muted',
|
|
86
|
+
flexShrink: 0,
|
|
87
|
+
}, children: formatDuration(span.duration_ms) })] }), hasChildren &&
|
|
88
|
+
isExpanded &&
|
|
89
|
+
span.children.map(child => (_jsx(SpanNode, { span: child, depth: depth + 1, selectedSpanId: selectedSpanId, onSelectSpan: onSelectSpan, expandedIds: expandedIds, toggleExpanded: toggleExpanded, traceMinTime: traceMinTime, traceDuration: traceDuration }, child.span_id)))] }));
|
|
90
|
+
};
|
|
91
|
+
export const OtelSpanTree = ({ spans, selectedSpanId, onSelectSpan, defaultExpandDepth = 2, }) => {
|
|
92
|
+
// Pre-compute which IDs should be expanded by default
|
|
93
|
+
const defaultExpanded = useMemo(() => {
|
|
94
|
+
const ids = new Set();
|
|
95
|
+
function walk(nodes, depth) {
|
|
96
|
+
for (const n of nodes) {
|
|
97
|
+
if (depth < defaultExpandDepth) {
|
|
98
|
+
ids.add(n.span_id);
|
|
99
|
+
}
|
|
100
|
+
if (n.children)
|
|
101
|
+
walk(n.children, depth + 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
walk(spans, 0);
|
|
105
|
+
return ids;
|
|
106
|
+
}, [spans, defaultExpandDepth]);
|
|
107
|
+
const [expandedIds, setExpandedIds] = useState(defaultExpanded);
|
|
108
|
+
const toggleExpanded = useCallback((id) => {
|
|
109
|
+
setExpandedIds(prev => {
|
|
110
|
+
const next = new Set(prev);
|
|
111
|
+
if (next.has(id)) {
|
|
112
|
+
next.delete(id);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
next.add(id);
|
|
116
|
+
}
|
|
117
|
+
return next;
|
|
118
|
+
});
|
|
119
|
+
}, []);
|
|
120
|
+
// Compute trace time bounds
|
|
121
|
+
const { traceMinTime, traceDuration } = useMemo(() => {
|
|
122
|
+
let min = Infinity;
|
|
123
|
+
let max = -Infinity;
|
|
124
|
+
function walk(nodes) {
|
|
125
|
+
for (const s of nodes) {
|
|
126
|
+
const start = toMs(s.start_time);
|
|
127
|
+
const end = toMs(s.end_time);
|
|
128
|
+
if (start < min)
|
|
129
|
+
min = start;
|
|
130
|
+
if (end > max)
|
|
131
|
+
max = end;
|
|
132
|
+
if (s.children)
|
|
133
|
+
walk(s.children);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
walk(spans);
|
|
137
|
+
if (min === Infinity)
|
|
138
|
+
return { traceMinTime: 0, traceDuration: 1 };
|
|
139
|
+
return { traceMinTime: min, traceDuration: max - min || 1 };
|
|
140
|
+
}, [spans]);
|
|
141
|
+
if (spans.length === 0) {
|
|
142
|
+
return (_jsx(Box, { sx: { p: 3, color: 'fg.muted', textAlign: 'center' }, children: _jsx(Text, { children: "No span tree to display." }) }));
|
|
143
|
+
}
|
|
144
|
+
return (_jsxs(Box, { sx: { width: '100%', overflow: 'auto' }, children: [_jsxs(Box, { sx: {
|
|
145
|
+
display: 'flex',
|
|
146
|
+
alignItems: 'center',
|
|
147
|
+
height: 28,
|
|
148
|
+
px: 2,
|
|
149
|
+
borderBottom: '2px solid',
|
|
150
|
+
borderColor: 'border.default',
|
|
151
|
+
bg: 'canvas.subtle',
|
|
152
|
+
}, children: [_jsx(Box, { sx: { width: 20 } }), _jsx(Box, { sx: { width: 8, mr: 1 } }), _jsx(Text, { sx: {
|
|
153
|
+
flex: '0 0 200px',
|
|
154
|
+
fontSize: 0,
|
|
155
|
+
fontWeight: 'bold',
|
|
156
|
+
color: 'fg.muted',
|
|
157
|
+
textTransform: 'uppercase',
|
|
158
|
+
letterSpacing: '0.05em',
|
|
159
|
+
}, children: "Span" }), _jsx(Text, { sx: {
|
|
160
|
+
flex: 1,
|
|
161
|
+
mx: 2,
|
|
162
|
+
fontSize: 0,
|
|
163
|
+
fontWeight: 'bold',
|
|
164
|
+
color: 'fg.muted',
|
|
165
|
+
textTransform: 'uppercase',
|
|
166
|
+
letterSpacing: '0.05em',
|
|
167
|
+
}, children: "Timeline" }), _jsx(Text, { sx: {
|
|
168
|
+
minWidth: 60,
|
|
169
|
+
textAlign: 'right',
|
|
170
|
+
fontSize: 0,
|
|
171
|
+
fontWeight: 'bold',
|
|
172
|
+
color: 'fg.muted',
|
|
173
|
+
textTransform: 'uppercase',
|
|
174
|
+
letterSpacing: '0.05em',
|
|
175
|
+
}, children: "Duration" })] }), spans.map(root => (_jsx(SpanNode, { span: root, depth: 0, selectedSpanId: selectedSpanId, onSelectSpan: onSelectSpan, expandedIds: expandedIds, toggleExpanded: toggleExpanded, traceMinTime: traceMinTime, traceDuration: traceDuration }, root.span_id)))] }));
|
|
176
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OtelSqlView – Ad-hoc DataFusion SQL query panel for the OTEL service.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Preset query dropdown (spans, logs, metrics)
|
|
6
|
+
* - Query history persisted to cookie `otel_sql_history` (max 10 entries)
|
|
7
|
+
* - ⌘/Ctrl + Enter to run
|
|
8
|
+
*
|
|
9
|
+
* Available tables: `spans`, `metrics`, `logs`.
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react';
|
|
12
|
+
export interface OtelSqlViewProps {
|
|
13
|
+
token?: string;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const OtelSqlView: React.FC<OtelSqlViewProps>;
|
|
@@ -0,0 +1,239 @@
|
|
|
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
|
+
* OtelSqlView – Ad-hoc DataFusion SQL query panel for the OTEL service.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Preset query dropdown (spans, logs, metrics)
|
|
11
|
+
* - Query history persisted to cookie `otel_sql_history` (max 10 entries)
|
|
12
|
+
* - ⌘/Ctrl + Enter to run
|
|
13
|
+
*
|
|
14
|
+
* Available tables: `spans`, `metrics`, `logs`.
|
|
15
|
+
*/
|
|
16
|
+
import { useState, useCallback } from 'react';
|
|
17
|
+
import { ActionList, ActionMenu, Box, Button, Text, Textarea, Spinner, } from '@primer/react';
|
|
18
|
+
import { HistoryIcon, TableIcon, XCircleIcon, DownloadIcon, } from '@primer/octicons-react';
|
|
19
|
+
import { useOtelQuery } from '../hooks';
|
|
20
|
+
// ── Cookie helpers ────────────────────────────────────────────────
|
|
21
|
+
const HISTORY_COOKIE = 'otel_sql_history';
|
|
22
|
+
const HISTORY_MAX = 10;
|
|
23
|
+
function readHistory() {
|
|
24
|
+
try {
|
|
25
|
+
const m = document.cookie.match(/(?:^|;\s*)otel_sql_history=([^;]+)/);
|
|
26
|
+
if (m)
|
|
27
|
+
return JSON.parse(decodeURIComponent(m[1]));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// ignore
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
function writeHistory(entries) {
|
|
35
|
+
try {
|
|
36
|
+
document.cookie = `${HISTORY_COOKIE}=${encodeURIComponent(JSON.stringify(entries))};path=/;max-age=31536000`;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function pushHistory(sql, prev) {
|
|
43
|
+
const deduped = prev.filter(s => s !== sql);
|
|
44
|
+
return [sql, ...deduped].slice(0, HISTORY_MAX);
|
|
45
|
+
}
|
|
46
|
+
const PRESETS = [
|
|
47
|
+
{
|
|
48
|
+
group: 'Spans',
|
|
49
|
+
label: 'Recent spans',
|
|
50
|
+
sql: 'SELECT trace_id, operation_name, service_name, start_time_unix_nano\nFROM spans\nORDER BY start_time_unix_nano DESC\nLIMIT 20',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
group: 'Spans',
|
|
54
|
+
label: 'Error spans',
|
|
55
|
+
sql: "SELECT trace_id, operation_name, service_name, status_code, status_message\nFROM spans\nWHERE status_code = 'ERROR'\nLIMIT 20",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
group: 'Spans',
|
|
59
|
+
label: 'Span count by service',
|
|
60
|
+
sql: 'SELECT service_name, COUNT(*) AS span_count\nFROM spans\nGROUP BY service_name\nORDER BY span_count DESC',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
group: 'Logs',
|
|
64
|
+
label: 'Recent logs',
|
|
65
|
+
sql: 'SELECT timestamp_unix_nano, severity_text, body, service_name\nFROM logs\nORDER BY timestamp_unix_nano DESC\nLIMIT 20',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
group: 'Logs',
|
|
69
|
+
label: 'Error logs',
|
|
70
|
+
sql: "SELECT timestamp_unix_nano, severity_text, body, service_name\nFROM logs\nWHERE severity_text IN ('ERROR', 'FATAL')\nLIMIT 20",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
group: 'Metrics',
|
|
74
|
+
label: 'Recent metrics',
|
|
75
|
+
sql: 'SELECT metric_name, value_double, metric_unit, service_name, timestamp_unix_nano\nFROM metrics\nORDER BY timestamp_unix_nano DESC\nLIMIT 20',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
group: 'Metrics',
|
|
79
|
+
label: 'Distinct metric names',
|
|
80
|
+
sql: 'SELECT DISTINCT metric_name, metric_unit\nFROM metrics\nORDER BY metric_name',
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
const PRESET_GROUPS = Array.from(new Set(PRESETS.map(p => p.group)));
|
|
84
|
+
export const OtelSqlView = ({ token, baseUrl = '', }) => {
|
|
85
|
+
const [sql, setSql] = useState(PRESETS[0].sql);
|
|
86
|
+
const [history, setHistory] = useState(() => readHistory());
|
|
87
|
+
const { rows, loading, error, execute, clear } = useOtelQuery({
|
|
88
|
+
token,
|
|
89
|
+
baseUrl,
|
|
90
|
+
});
|
|
91
|
+
const handleRun = useCallback(() => {
|
|
92
|
+
const trimmed = sql.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
return;
|
|
95
|
+
const next = pushHistory(trimmed, history);
|
|
96
|
+
setHistory(next);
|
|
97
|
+
writeHistory(next);
|
|
98
|
+
execute(trimmed);
|
|
99
|
+
}, [sql, history, execute]);
|
|
100
|
+
const handleKeyDown = (e) => {
|
|
101
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
handleRun();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
107
|
+
return (_jsxs(Box, { sx: {
|
|
108
|
+
display: 'flex',
|
|
109
|
+
flexDirection: 'column',
|
|
110
|
+
flex: 1,
|
|
111
|
+
minHeight: 0,
|
|
112
|
+
height: '100%',
|
|
113
|
+
gap: 3,
|
|
114
|
+
p: 4,
|
|
115
|
+
bg: 'canvas.default',
|
|
116
|
+
color: 'fg.default',
|
|
117
|
+
overflow: 'auto',
|
|
118
|
+
}, children: [_jsxs(Box, { sx: { 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", sx: { fontFamily: 'mono', fontSize: 0 }, children: "spans" }), ', ', _jsx(Box, { as: "code", sx: { fontFamily: 'mono', fontSize: 0 }, children: "metrics" }), ', ', _jsx(Box, { as: "code", sx: { fontFamily: 'mono', fontSize: 0 }, children: "logs" })] })] }), _jsxs(Box, { sx: { 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, { sx: { 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(Box, { sx: {
|
|
119
|
+
p: 3,
|
|
120
|
+
bg: 'danger.subtle',
|
|
121
|
+
borderRadius: 2,
|
|
122
|
+
border: '1px solid',
|
|
123
|
+
borderColor: 'danger.muted',
|
|
124
|
+
}, children: _jsx(Text, { sx: { color: 'danger.fg', fontFamily: 'mono', fontSize: 1 }, children: error }) })), !error && rows.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { sx: { 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", sx: {
|
|
125
|
+
ml: 'auto',
|
|
126
|
+
display: 'flex',
|
|
127
|
+
alignItems: 'center',
|
|
128
|
+
gap: 1,
|
|
129
|
+
px: 2,
|
|
130
|
+
py: '2px',
|
|
131
|
+
border: 'none',
|
|
132
|
+
borderRadius: 2,
|
|
133
|
+
bg: 'transparent',
|
|
134
|
+
color: 'fg.muted',
|
|
135
|
+
cursor: 'pointer',
|
|
136
|
+
fontSize: 0,
|
|
137
|
+
'&:hover': { color: 'danger.fg', bg: 'danger.subtle' },
|
|
138
|
+
}, children: [_jsx(XCircleIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "Clear" })] }), _jsxs(Box, { as: "button", onClick: () => {
|
|
139
|
+
const header = columns.join(',');
|
|
140
|
+
const body = rows
|
|
141
|
+
.map(row => columns
|
|
142
|
+
.map(col => {
|
|
143
|
+
const v = String(row[col] ?? '');
|
|
144
|
+
return v.includes(',') ||
|
|
145
|
+
v.includes('"') ||
|
|
146
|
+
v.includes('\n')
|
|
147
|
+
? `"${v.replace(/"/g, '""')}"`
|
|
148
|
+
: v;
|
|
149
|
+
})
|
|
150
|
+
.join(','))
|
|
151
|
+
.join('\n');
|
|
152
|
+
const blob = new Blob([header + '\n' + body], {
|
|
153
|
+
type: 'text/csv',
|
|
154
|
+
});
|
|
155
|
+
const url = URL.createObjectURL(blob);
|
|
156
|
+
const a = document.createElement('a');
|
|
157
|
+
a.href = url;
|
|
158
|
+
a.download = 'otel-query-result.csv';
|
|
159
|
+
a.click();
|
|
160
|
+
URL.revokeObjectURL(url);
|
|
161
|
+
}, title: "Download as CSV", sx: {
|
|
162
|
+
display: 'flex',
|
|
163
|
+
alignItems: 'center',
|
|
164
|
+
gap: 1,
|
|
165
|
+
px: 2,
|
|
166
|
+
py: '2px',
|
|
167
|
+
border: 'none',
|
|
168
|
+
borderRadius: 2,
|
|
169
|
+
bg: 'transparent',
|
|
170
|
+
color: 'fg.muted',
|
|
171
|
+
cursor: 'pointer',
|
|
172
|
+
fontSize: 0,
|
|
173
|
+
'&:hover': { color: 'accent.fg', bg: 'accent.subtle' },
|
|
174
|
+
}, children: [_jsx(DownloadIcon, { size: 12 }), _jsx(Text, { sx: { fontSize: 0 }, children: "CSV" })] })] }), _jsx(Box, { sx: {
|
|
175
|
+
flex: 1,
|
|
176
|
+
minHeight: 200,
|
|
177
|
+
overflow: 'auto',
|
|
178
|
+
border: '1px solid',
|
|
179
|
+
borderColor: 'border.default',
|
|
180
|
+
borderRadius: 2,
|
|
181
|
+
}, children: _jsxs(Box, { as: "table", sx: { width: '100%', borderCollapse: 'collapse' }, children: [_jsx(Box, { as: "thead", children: _jsx(Box, { as: "tr", sx: { bg: 'canvas.subtle' }, children: columns.map(col => (_jsx(Box, { as: "th", sx: {
|
|
182
|
+
px: 3,
|
|
183
|
+
py: 2,
|
|
184
|
+
textAlign: 'left',
|
|
185
|
+
fontWeight: 'bold',
|
|
186
|
+
fontSize: 0,
|
|
187
|
+
color: 'fg.muted',
|
|
188
|
+
borderBottom: '1px solid',
|
|
189
|
+
borderColor: 'border.default',
|
|
190
|
+
whiteSpace: 'nowrap',
|
|
191
|
+
}, children: col }, col))) }) }), _jsx(Box, { as: "tbody", children: rows.map((row, ri) => (_jsx(Box, { as: "tr", sx: {
|
|
192
|
+
'&:hover': { bg: 'canvas.subtle' },
|
|
193
|
+
'&:not(:last-child) td': {
|
|
194
|
+
borderBottom: '1px solid',
|
|
195
|
+
borderColor: 'border.muted',
|
|
196
|
+
},
|
|
197
|
+
}, children: columns.map(col => (_jsx(Box, { as: "td", sx: {
|
|
198
|
+
px: 3,
|
|
199
|
+
py: 2,
|
|
200
|
+
fontSize: 0,
|
|
201
|
+
fontFamily: 'mono',
|
|
202
|
+
maxWidth: '300px',
|
|
203
|
+
overflow: 'hidden',
|
|
204
|
+
textOverflow: 'ellipsis',
|
|
205
|
+
whiteSpace: 'nowrap',
|
|
206
|
+
}, children: String(row[col] ?? '') }, col))) }, ri))) })] }) })] })), !error && !loading && rows.length === 0 && (_jsx(Box, { sx: {
|
|
207
|
+
flex: 1,
|
|
208
|
+
display: 'flex',
|
|
209
|
+
alignItems: 'center',
|
|
210
|
+
justifyContent: 'center',
|
|
211
|
+
color: 'fg.muted',
|
|
212
|
+
}, children: _jsx(Text, { sx: { fontSize: 1 }, children: "Run a query to see results." }) })), history.length > 0 && (_jsxs(Box, { children: [_jsxs(Box, { sx: { 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(Box, { sx: {
|
|
213
|
+
display: 'flex',
|
|
214
|
+
flexDirection: 'column',
|
|
215
|
+
border: '1px solid',
|
|
216
|
+
borderColor: 'border.default',
|
|
217
|
+
borderRadius: 2,
|
|
218
|
+
overflow: 'hidden',
|
|
219
|
+
}, children: history.map((entry, i) => (_jsxs(Box, { sx: {
|
|
220
|
+
display: 'flex',
|
|
221
|
+
alignItems: 'center',
|
|
222
|
+
gap: 2,
|
|
223
|
+
px: 3,
|
|
224
|
+
py: '6px',
|
|
225
|
+
cursor: 'pointer',
|
|
226
|
+
bg: i % 2 === 0 ? 'canvas.default' : 'canvas.subtle',
|
|
227
|
+
borderTop: i > 0 ? '1px solid' : 'none',
|
|
228
|
+
borderColor: 'border.muted',
|
|
229
|
+
'&:hover': { bg: 'accent.subtle' },
|
|
230
|
+
}, onClick: () => setSql(entry), children: [_jsx(Text, { sx: {
|
|
231
|
+
fontFamily: 'mono',
|
|
232
|
+
fontSize: 0,
|
|
233
|
+
color: 'fg.default',
|
|
234
|
+
flex: 1,
|
|
235
|
+
overflow: 'hidden',
|
|
236
|
+
textOverflow: 'ellipsis',
|
|
237
|
+
whiteSpace: 'nowrap',
|
|
238
|
+
}, children: entry.replace(/\s+/g, ' ') }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle', flexShrink: 0 }, children: "restore \u2191" })] }, i))) })] }))] }));
|
|
239
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OtelSystemView – System statistics panel for the OTEL service.
|
|
3
|
+
*
|
|
4
|
+
* Calls GET /api/otel/v1/system/ (platform_admin only) and renders:
|
|
5
|
+
* - Process memory / CPU
|
|
6
|
+
* - Disk usage
|
|
7
|
+
* - Per-table row counts, distinct users and disk
|
|
8
|
+
* - Total distinct users
|
|
9
|
+
*/
|
|
10
|
+
import React from 'react';
|
|
11
|
+
export interface OtelSystemViewProps {
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
token?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const OtelSystemView: React.FC<OtelSystemViewProps>;
|