@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.
Files changed (145) hide show
  1. package/README.md +1 -1
  2. package/lib/api/constants.d.ts +6 -0
  3. package/lib/api/constants.js +6 -0
  4. package/lib/api/index.d.ts +1 -0
  5. package/lib/api/index.js +1 -0
  6. package/lib/api/otel/index.d.ts +12 -0
  7. package/lib/api/otel/index.js +16 -0
  8. package/lib/api/otel/logs.d.ts +19 -0
  9. package/lib/api/otel/logs.js +43 -0
  10. package/lib/api/otel/metrics.d.ts +31 -0
  11. package/lib/api/otel/metrics.js +65 -0
  12. package/lib/api/otel/query.d.ts +16 -0
  13. package/lib/api/otel/query.js +37 -0
  14. package/lib/api/otel/services.d.ts +39 -0
  15. package/lib/api/otel/services.js +81 -0
  16. package/lib/api/otel/traces.d.ts +24 -0
  17. package/lib/api/otel/traces.js +53 -0
  18. package/lib/api/otel/types.d.ts +112 -0
  19. package/lib/api/otel/types.js +5 -0
  20. package/lib/api/runtimes/checkpoints.d.ts +122 -0
  21. package/lib/api/runtimes/checkpoints.js +118 -0
  22. package/lib/api/runtimes/index.d.ts +1 -0
  23. package/lib/api/runtimes/index.js +1 -0
  24. package/lib/api/runtimes/runtimes.d.ts +84 -0
  25. package/lib/api/runtimes/runtimes.js +50 -0
  26. package/lib/components/auth/Login.js +1 -1
  27. package/lib/components/display/BusyDots.d.ts +9 -0
  28. package/lib/components/display/BusyDots.js +31 -0
  29. package/lib/components/display/LiveRelativeTime.d.ts +10 -0
  30. package/lib/components/display/LiveRelativeTime.js +21 -0
  31. package/lib/components/display/index.d.ts +2 -0
  32. package/lib/components/display/index.js +2 -0
  33. package/lib/components/flashes/FlashSurveys.js +1 -1
  34. package/lib/components/index.d.ts +1 -0
  35. package/lib/components/index.js +1 -0
  36. package/lib/components/navbar/SubdomainNavBar.js +1 -1
  37. package/lib/components/progress/ConsumptionBar.js +6 -7
  38. package/lib/components/progress/CreditsIndicator.js +2 -2
  39. package/lib/components/progress/consumption.d.ts +12 -0
  40. package/lib/components/progress/consumption.js +31 -0
  41. package/lib/components/progress/index.d.ts +1 -0
  42. package/lib/components/progress/index.js +1 -0
  43. package/lib/components/sparklines/Sparklines.d.ts +16 -0
  44. package/lib/components/sparklines/Sparklines.js +65 -0
  45. package/lib/components/sparklines/SparklinesLine.d.ts +8 -0
  46. package/lib/components/sparklines/SparklinesLine.js +37 -0
  47. package/lib/components/sparklines/dataProcessing.d.ts +25 -0
  48. package/lib/components/sparklines/dataProcessing.js +35 -0
  49. package/lib/components/sparklines/index.d.ts +4 -0
  50. package/lib/components/sparklines/index.js +7 -0
  51. package/lib/components/sparklines/types.d.ts +36 -0
  52. package/lib/components/sparklines/types.js +5 -0
  53. package/lib/components/storage/ContentsBrowser.js +17 -1
  54. package/lib/components/subnav/SubNav.js +1 -1
  55. package/lib/config/Configuration.d.ts +4 -0
  56. package/lib/hooks/useCache.d.ts +6 -63
  57. package/lib/hooks/useCache.js +35 -205
  58. package/lib/hooks/useProjects.d.ts +1 -1
  59. package/lib/index.d.ts +2 -0
  60. package/lib/index.js +4 -0
  61. package/lib/models/ItemDTO.js +1 -1
  62. package/lib/models/RolesPlatform.js +2 -2
  63. package/lib/models/User.d.ts +2 -0
  64. package/lib/models/User.js +4 -1
  65. package/lib/otel/client/OtelClient.d.ts +93 -0
  66. package/lib/otel/client/OtelClient.js +232 -0
  67. package/lib/otel/client/index.d.ts +2 -0
  68. package/lib/otel/client/index.js +5 -0
  69. package/lib/otel/hooks/index.d.ts +186 -0
  70. package/lib/otel/hooks/index.js +532 -0
  71. package/lib/otel/index.d.ts +34 -0
  72. package/lib/otel/index.js +23 -0
  73. package/lib/otel/types.d.ts +190 -0
  74. package/lib/otel/types.js +5 -0
  75. package/lib/otel/utils.d.ts +33 -0
  76. package/lib/otel/utils.js +181 -0
  77. package/lib/otel/views/OtelLive.d.ts +12 -0
  78. package/lib/otel/views/OtelLive.js +372 -0
  79. package/lib/otel/views/OtelLogsList.d.ts +11 -0
  80. package/lib/otel/views/OtelLogsList.js +137 -0
  81. package/lib/otel/views/OtelMetricsChart.d.ts +22 -0
  82. package/lib/otel/views/OtelMetricsChart.js +300 -0
  83. package/lib/otel/views/OtelMetricsList.d.ts +15 -0
  84. package/lib/otel/views/OtelMetricsList.js +213 -0
  85. package/lib/otel/views/OtelSearchBar.d.ts +11 -0
  86. package/lib/otel/views/OtelSearchBar.js +22 -0
  87. package/lib/otel/views/OtelSpanDetail.d.ts +11 -0
  88. package/lib/otel/views/OtelSpanDetail.js +172 -0
  89. package/lib/otel/views/OtelSpanTree.d.ts +11 -0
  90. package/lib/otel/views/OtelSpanTree.js +176 -0
  91. package/lib/otel/views/OtelSqlView.d.ts +16 -0
  92. package/lib/otel/views/OtelSqlView.js +239 -0
  93. package/lib/otel/views/OtelSystemView.d.ts +15 -0
  94. package/lib/otel/views/OtelSystemView.js +75 -0
  95. package/lib/otel/views/OtelTimeline.d.ts +11 -0
  96. package/lib/otel/views/OtelTimeline.js +101 -0
  97. package/lib/otel/views/OtelTimelineRangeSlider.d.ts +16 -0
  98. package/lib/otel/views/OtelTimelineRangeSlider.js +338 -0
  99. package/lib/otel/views/OtelTracesList.d.ts +13 -0
  100. package/lib/otel/views/OtelTracesList.js +199 -0
  101. package/lib/otel/views/index.d.ts +20 -0
  102. package/lib/otel/views/index.js +21 -0
  103. package/lib/state/storage/IAMStorage.d.ts +2 -1
  104. package/lib/state/substates/CoreState.js +7 -6
  105. package/lib/utils/Date.d.ts +6 -0
  106. package/lib/utils/Date.js +37 -0
  107. package/lib/utils/Jwt.d.ts +42 -0
  108. package/lib/utils/Jwt.js +44 -0
  109. package/lib/utils/index.d.ts +1 -0
  110. package/lib/utils/index.js +1 -0
  111. package/lib/views/iam/SignInSimple.d.ts +43 -0
  112. package/lib/views/iam/SignInSimple.js +113 -0
  113. package/lib/views/iam/index.d.ts +2 -0
  114. package/lib/views/iam/index.js +5 -0
  115. package/lib/views/iam-tokens/IAMTokenEdit.d.ts +5 -1
  116. package/lib/views/iam-tokens/IAMTokenEdit.js +54 -5
  117. package/lib/views/iam-tokens/IAMTokenNew.js +2 -2
  118. package/lib/views/iam-tokens/IAMTokens.d.ts +4 -2
  119. package/lib/views/iam-tokens/IAMTokens.js +68 -36
  120. package/lib/views/iam-tokens/Tokens.js +63 -31
  121. package/lib/views/index.d.ts +3 -1
  122. package/lib/views/index.js +3 -1
  123. package/lib/views/otel/DashboardView.d.ts +16 -0
  124. package/lib/views/otel/DashboardView.js +4 -0
  125. package/lib/views/otel/LogsView.d.ts +12 -0
  126. package/lib/views/otel/LogsView.js +4 -0
  127. package/lib/views/otel/MetricsView.d.ts +12 -0
  128. package/lib/views/otel/MetricsView.js +4 -0
  129. package/lib/views/otel/OtelHeader.d.ts +33 -0
  130. package/lib/views/otel/OtelHeader.js +105 -0
  131. package/lib/views/otel/SqlView.d.ts +9 -0
  132. package/lib/views/otel/SqlView.js +4 -0
  133. package/lib/views/otel/SystemView.d.ts +9 -0
  134. package/lib/views/otel/SystemView.js +4 -0
  135. package/lib/views/otel/TracesView.d.ts +12 -0
  136. package/lib/views/otel/TracesView.js +4 -0
  137. package/lib/views/otel/index.d.ts +16 -0
  138. package/lib/views/otel/index.js +12 -0
  139. package/lib/views/otel/simpleAuthStore.d.ts +21 -0
  140. package/lib/views/otel/simpleAuthStore.js +22 -0
  141. package/lib/views/profile/UserBadge.d.ts +20 -0
  142. package/lib/views/profile/UserBadge.js +101 -0
  143. package/lib/views/profile/index.d.ts +2 -0
  144. package/lib/views/profile/index.js +5 -0
  145. 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>;