@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,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Button, Spinner, Text } from '@primer/react';
|
|
3
|
+
import { SyncIcon } from '@primer/octicons-react';
|
|
4
|
+
import { useOtelSystem } from '../hooks';
|
|
5
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
6
|
+
function fmtBytes(bytes) {
|
|
7
|
+
if (bytes >= 1024 ** 3)
|
|
8
|
+
return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
|
|
9
|
+
if (bytes >= 1024 ** 2)
|
|
10
|
+
return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
|
|
11
|
+
if (bytes >= 1024)
|
|
12
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
13
|
+
return `${bytes} B`;
|
|
14
|
+
}
|
|
15
|
+
// ── Sub-components ────────────────────────────────────────────────
|
|
16
|
+
const CARD_SX = {
|
|
17
|
+
bg: 'canvas.subtle',
|
|
18
|
+
border: '1px solid',
|
|
19
|
+
borderColor: 'border.default',
|
|
20
|
+
borderRadius: 2,
|
|
21
|
+
p: 3,
|
|
22
|
+
mb: 3,
|
|
23
|
+
};
|
|
24
|
+
const LABEL_SX = {
|
|
25
|
+
display: 'block',
|
|
26
|
+
fontSize: 0,
|
|
27
|
+
fontWeight: 'bold',
|
|
28
|
+
color: 'fg.muted',
|
|
29
|
+
mb: 1,
|
|
30
|
+
};
|
|
31
|
+
const GRID_SX = {
|
|
32
|
+
display: 'grid',
|
|
33
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))',
|
|
34
|
+
gap: 3,
|
|
35
|
+
};
|
|
36
|
+
const StatCell = ({ label, value }) => (_jsxs(Box, { children: [_jsx(Text, { sx: LABEL_SX, children: label }), _jsx(Text, { sx: { fontSize: 1, fontFamily: 'mono' }, children: value })] }));
|
|
37
|
+
const SystemViewContent = ({ data }) => {
|
|
38
|
+
const { process: proc, disk, tables, total_distinct_users } = data;
|
|
39
|
+
return (_jsxs(Box, { children: [_jsxs(Box, { sx: CARD_SX, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', mb: 2, display: 'block' }, children: "Process" }), proc?.error ? (_jsx(Text, { sx: { color: 'attention.fg', fontSize: 0 }, children: proc.error })) : (_jsxs(Box, { sx: GRID_SX, children: [_jsx(StatCell, { label: "RSS Memory", value: fmtBytes(proc?.memory_rss_bytes ?? 0) }), _jsx(StatCell, { label: "VMS Memory", value: fmtBytes(proc?.memory_vms_bytes ?? 0) }), _jsx(StatCell, { label: "CPU", value: `${(proc?.cpu_percent ?? 0).toFixed(1)} %` }), _jsx(StatCell, { label: "Threads", value: String(proc?.num_threads ?? '?') })] }))] }), _jsxs(Box, { sx: CARD_SX, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', mb: 2, display: 'block' }, children: "Disk" }), disk?.error ? (_jsx(Text, { sx: { color: 'attention.fg', fontSize: 0 }, children: disk.error })) : (_jsxs(Box, { sx: GRID_SX, children: [_jsx(StatCell, { label: "Data Dir", value: disk?.data_dir ?? '?' }), _jsx(StatCell, { label: "Total", value: fmtBytes(disk?.total_bytes ?? 0) }), _jsx(StatCell, { label: "Used", value: `${fmtBytes(disk?.used_bytes ?? 0)} (${(disk?.used_percent ?? 0).toFixed(1)}%)` }), _jsx(StatCell, { label: "Free", value: fmtBytes(disk?.free_bytes ?? 0) })] }))] }), tables && Object.keys(tables).length > 0 && (_jsxs(Box, { sx: CARD_SX, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', mb: 2, display: 'block' }, children: "Tables" }), _jsxs(Box, { as: "table", sx: {
|
|
40
|
+
width: '100%',
|
|
41
|
+
borderCollapse: 'collapse',
|
|
42
|
+
fontSize: 0,
|
|
43
|
+
fontFamily: 'mono',
|
|
44
|
+
}, children: [_jsx(Box, { as: "thead", children: _jsx(Box, { as: "tr", children: ['Table', 'Rows', 'Users', 'Disk'].map(h => (_jsx(Box, { as: "th", sx: {
|
|
45
|
+
textAlign: h === 'Table' ? 'left' : 'right',
|
|
46
|
+
pb: 1,
|
|
47
|
+
color: 'fg.muted',
|
|
48
|
+
fontWeight: 'bold',
|
|
49
|
+
borderBottom: '1px solid',
|
|
50
|
+
borderColor: 'border.default',
|
|
51
|
+
}, children: h }, h))) }) }), _jsx(Box, { as: "tbody", children: Object.entries(tables).map(([tbl, info]) => (_jsxs(Box, { as: "tr", children: [_jsx(Box, { as: "td", sx: { py: 1, pr: 3, color: 'accent.fg' }, children: tbl }), info?.error ? (_jsx(Box, { as: "td", colSpan: 3, sx: { color: 'danger.fg', py: 1 }, children: info.error })) : (_jsxs(_Fragment, { children: [_jsx(Box, { as: "td", sx: { textAlign: 'right', py: 1, pr: 3 }, children: (info.row_count ?? 0).toLocaleString() }), _jsx(Box, { as: "td", sx: { textAlign: 'right', py: 1, pr: 3 }, children: info.distinct_users ?? 0 }), _jsx(Box, { as: "td", sx: { textAlign: 'right', py: 1 }, children: fmtBytes(info.disk_bytes ?? 0) })] }))] }, tbl))) })] })] })), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Total distinct users across all tables:", ' ', _jsx(Text, { sx: { fontWeight: 'bold', color: 'fg.default' }, children: total_distinct_users ?? '?' })] })] }));
|
|
52
|
+
};
|
|
53
|
+
export const OtelSystemView = ({ baseUrl = '', token, }) => {
|
|
54
|
+
const { data, loading, error, refresh } = useOtelSystem({ baseUrl, token });
|
|
55
|
+
return (_jsxs(Box, { sx: {
|
|
56
|
+
display: 'flex',
|
|
57
|
+
flexDirection: 'column',
|
|
58
|
+
flex: 1,
|
|
59
|
+
minHeight: 0,
|
|
60
|
+
overflow: 'auto',
|
|
61
|
+
p: 3,
|
|
62
|
+
}, children: [_jsxs(Box, { sx: {
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
justifyContent: 'space-between',
|
|
66
|
+
mb: 3,
|
|
67
|
+
flexShrink: 0,
|
|
68
|
+
}, 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, { sx: { 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(Box, { sx: {
|
|
69
|
+
bg: 'danger.subtle',
|
|
70
|
+
border: '1px solid',
|
|
71
|
+
borderColor: 'danger.muted',
|
|
72
|
+
borderRadius: 2,
|
|
73
|
+
p: 3,
|
|
74
|
+
}, 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." }))] }));
|
|
75
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OtelTimeline – Horizontal waterfall timeline for trace spans.
|
|
3
|
+
*
|
|
4
|
+
* Each span renders as a coloured bar positioned relative to the trace's
|
|
5
|
+
* overall time window. Uses Primer React components for consistent theming.
|
|
6
|
+
*
|
|
7
|
+
* @module otel/OtelTimeline
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import type { OtelTimelineProps } from '../types';
|
|
11
|
+
export declare const OtelTimeline: React.FC<OtelTimelineProps>;
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
* OtelTimeline – Horizontal waterfall timeline for trace spans.
|
|
8
|
+
*
|
|
9
|
+
* Each span renders as a coloured bar positioned relative to the trace's
|
|
10
|
+
* overall time window. Uses Primer React components for consistent theming.
|
|
11
|
+
*
|
|
12
|
+
* @module otel/OtelTimeline
|
|
13
|
+
*/
|
|
14
|
+
import { useMemo } from 'react';
|
|
15
|
+
import { Box, Text } from '@primer/react';
|
|
16
|
+
import { toMs, formatDuration, serviceColor } from '../utils';
|
|
17
|
+
export const OtelTimeline = ({ spans, barHeight = 24, selectedSpanId, onSelectSpan, }) => {
|
|
18
|
+
const { minTime, maxTime, sortedSpans } = useMemo(() => {
|
|
19
|
+
if (spans.length === 0)
|
|
20
|
+
return { minTime: 0, maxTime: 1, sortedSpans: [] };
|
|
21
|
+
const sorted = [...spans].sort((a, b) => toMs(a.start_time) - toMs(b.start_time));
|
|
22
|
+
let min = Infinity;
|
|
23
|
+
let max = -Infinity;
|
|
24
|
+
for (const s of sorted) {
|
|
25
|
+
const start = toMs(s.start_time);
|
|
26
|
+
const end = toMs(s.end_time);
|
|
27
|
+
if (start < min)
|
|
28
|
+
min = start;
|
|
29
|
+
if (end > max)
|
|
30
|
+
max = end;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
minTime: min,
|
|
34
|
+
maxTime: max || min + 1,
|
|
35
|
+
sortedSpans: sorted,
|
|
36
|
+
};
|
|
37
|
+
}, [spans]);
|
|
38
|
+
const totalDuration = maxTime - minTime || 1;
|
|
39
|
+
if (sortedSpans.length === 0) {
|
|
40
|
+
return (_jsx(Box, { sx: { p: 3, color: 'fg.muted', textAlign: 'center' }, children: _jsx(Text, { children: "No spans to display." }) }));
|
|
41
|
+
}
|
|
42
|
+
return (_jsxs(Box, { sx: { width: '100%', overflowX: 'auto', py: 1 }, children: [sortedSpans.map(span => {
|
|
43
|
+
const startOffset = ((toMs(span.start_time) - minTime) / totalDuration) * 100;
|
|
44
|
+
const width = Math.max(((span.duration_ms || toMs(span.end_time) - toMs(span.start_time)) /
|
|
45
|
+
totalDuration) *
|
|
46
|
+
100, 0.4);
|
|
47
|
+
const color = serviceColor(span.service_name);
|
|
48
|
+
const isSelected = selectedSpanId === span.span_id;
|
|
49
|
+
const indent = (span.depth ?? 0) * 16;
|
|
50
|
+
return (_jsxs(Box, { sx: {
|
|
51
|
+
display: 'flex',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
height: barHeight,
|
|
54
|
+
mb: '2px',
|
|
55
|
+
cursor: 'pointer',
|
|
56
|
+
bg: isSelected ? 'accent.subtle' : 'canvas.default',
|
|
57
|
+
borderRadius: 1,
|
|
58
|
+
':hover': {
|
|
59
|
+
bg: isSelected ? 'accent.subtle' : 'canvas.subtle',
|
|
60
|
+
},
|
|
61
|
+
}, onClick: () => onSelectSpan?.(span), title: `${span.service_name} / ${span.span_name} — ${formatDuration(span.duration_ms)}`, children: [_jsx(Text, { sx: {
|
|
62
|
+
width: 200,
|
|
63
|
+
minWidth: 200,
|
|
64
|
+
pl: `${indent}px`,
|
|
65
|
+
pr: 2,
|
|
66
|
+
overflow: 'hidden',
|
|
67
|
+
textOverflow: 'ellipsis',
|
|
68
|
+
whiteSpace: 'nowrap',
|
|
69
|
+
fontSize: 1,
|
|
70
|
+
fontFamily: 'mono',
|
|
71
|
+
color: 'fg.muted',
|
|
72
|
+
}, children: span.span_name }), _jsx(Box, { sx: { flex: 1, position: 'relative', height: '100%' }, children: _jsx(Box, { sx: {
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
left: `${startOffset}%`,
|
|
75
|
+
width: `${width}%`,
|
|
76
|
+
height: barHeight - 6,
|
|
77
|
+
top: '3px',
|
|
78
|
+
bg: color,
|
|
79
|
+
borderRadius: 1,
|
|
80
|
+
opacity: isSelected ? 1 : 0.8,
|
|
81
|
+
border: isSelected ? '2px solid' : 'none',
|
|
82
|
+
borderColor: 'accent.emphasis',
|
|
83
|
+
} }) }), _jsx(Text, { sx: {
|
|
84
|
+
minWidth: 64,
|
|
85
|
+
textAlign: 'right',
|
|
86
|
+
pl: 1,
|
|
87
|
+
fontSize: 0,
|
|
88
|
+
fontFamily: 'mono',
|
|
89
|
+
color: 'fg.muted',
|
|
90
|
+
}, children: formatDuration(span.duration_ms) })] }, span.span_id));
|
|
91
|
+
}), _jsxs(Box, { sx: {
|
|
92
|
+
borderTop: '1px solid',
|
|
93
|
+
borderColor: 'border.default',
|
|
94
|
+
mt: 2,
|
|
95
|
+
pt: 2,
|
|
96
|
+
display: 'flex',
|
|
97
|
+
justifyContent: 'space-between',
|
|
98
|
+
fontSize: 0,
|
|
99
|
+
color: 'fg.muted',
|
|
100
|
+
}, children: [_jsxs(Text, { children: [sortedSpans.length, " span", sortedSpans.length !== 1 ? 's' : ''] }), _jsxs(Text, { sx: { fontFamily: 'mono' }, children: ["Total: ", formatDuration(totalDuration)] })] })] }));
|
|
101
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OtelTimelineRangeSlider – A modern dual-handle timeline range slider
|
|
3
|
+
* for selecting time windows over OTEL signals.
|
|
4
|
+
*
|
|
5
|
+
* Built with Primer React `Box` / `Text` + native pointer events for
|
|
6
|
+
* smooth drag interaction. Fully theme-aware — inherits all colors
|
|
7
|
+
* from the active Primer theme via CSS functional tokens.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by react-timeline-range-slider (d3-scale + react-compound-slider)
|
|
10
|
+
* but implemented from scratch with zero external dependencies beyond Primer.
|
|
11
|
+
*
|
|
12
|
+
* @module otel/OtelTimelineRangeSlider
|
|
13
|
+
*/
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import type { OtelTimelineRangeSliderProps } from '../types';
|
|
16
|
+
export declare const OtelTimelineRangeSlider: React.FC<OtelTimelineRangeSliderProps>;
|
|
@@ -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>;
|