@datalayer/core 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +1 -1
  2. package/lib/api/constants.d.ts +3 -0
  3. package/lib/api/constants.js +3 -0
  4. package/lib/api/index.d.ts +1 -0
  5. package/lib/api/index.js +1 -0
  6. package/lib/api/otel/index.d.ts +12 -0
  7. package/lib/api/otel/index.js +16 -0
  8. package/lib/api/otel/logs.d.ts +19 -0
  9. package/lib/api/otel/logs.js +43 -0
  10. package/lib/api/otel/metrics.d.ts +31 -0
  11. package/lib/api/otel/metrics.js +65 -0
  12. package/lib/api/otel/query.d.ts +16 -0
  13. package/lib/api/otel/query.js +37 -0
  14. package/lib/api/otel/services.d.ts +39 -0
  15. package/lib/api/otel/services.js +81 -0
  16. package/lib/api/otel/traces.d.ts +24 -0
  17. package/lib/api/otel/traces.js +53 -0
  18. package/lib/api/otel/types.d.ts +112 -0
  19. package/lib/api/otel/types.js +5 -0
  20. package/lib/api/spacer/index.d.ts +1 -2
  21. package/lib/api/spacer/index.js +1 -2
  22. package/lib/components/avatars/BoringAvatar.d.ts +3 -1
  23. package/lib/components/avatars/BoringAvatar.js +15 -14
  24. package/lib/components/avatars/BoringAvatar.stories.d.ts +2 -1
  25. package/lib/components/storage/ContentsBrowser.d.ts +6 -0
  26. package/lib/components/storage/ContentsBrowser.js +7 -8
  27. package/lib/config/Configuration.d.ts +4 -0
  28. package/lib/hooks/index.d.ts +2 -0
  29. package/lib/hooks/index.js +2 -0
  30. package/lib/hooks/useCache.d.ts +16 -40
  31. package/lib/hooks/useCache.js +28 -233
  32. package/lib/hooks/useProjectStore.d.ts +58 -0
  33. package/lib/hooks/useProjectStore.js +64 -0
  34. package/lib/hooks/useProjects.d.ts +590 -0
  35. package/lib/hooks/useProjects.js +166 -0
  36. package/lib/index.d.ts +2 -1
  37. package/lib/index.js +4 -2
  38. package/lib/models/Page.d.ts +2 -0
  39. package/lib/otel/OtelLive.d.ts +12 -0
  40. package/lib/otel/OtelLive.js +354 -0
  41. package/lib/otel/OtelLogsList.d.ts +11 -0
  42. package/lib/otel/OtelLogsList.js +137 -0
  43. package/lib/otel/OtelMetricsChart.d.ts +22 -0
  44. package/lib/otel/OtelMetricsChart.js +300 -0
  45. package/lib/otel/OtelMetricsList.d.ts +15 -0
  46. package/lib/otel/OtelMetricsList.js +213 -0
  47. package/lib/otel/OtelSearchBar.d.ts +11 -0
  48. package/lib/otel/OtelSearchBar.js +22 -0
  49. package/lib/otel/OtelSpanDetail.d.ts +11 -0
  50. package/lib/otel/OtelSpanDetail.js +172 -0
  51. package/lib/otel/OtelSpanTree.d.ts +11 -0
  52. package/lib/otel/OtelSpanTree.js +176 -0
  53. package/lib/otel/OtelSqlView.d.ts +16 -0
  54. package/lib/otel/OtelSqlView.js +239 -0
  55. package/lib/otel/OtelSystemView.d.ts +15 -0
  56. package/lib/otel/OtelSystemView.js +75 -0
  57. package/lib/otel/OtelTimeline.d.ts +11 -0
  58. package/lib/otel/OtelTimeline.js +101 -0
  59. package/lib/otel/OtelTimelineRangeSlider.d.ts +16 -0
  60. package/lib/otel/OtelTimelineRangeSlider.js +338 -0
  61. package/lib/otel/OtelTracesList.d.ts +13 -0
  62. package/lib/otel/OtelTracesList.js +199 -0
  63. package/lib/otel/hooks.d.ts +172 -0
  64. package/lib/otel/hooks.js +490 -0
  65. package/lib/otel/index.d.ts +25 -0
  66. package/lib/otel/index.js +19 -0
  67. package/lib/otel/types.d.ts +190 -0
  68. package/lib/otel/types.js +5 -0
  69. package/lib/otel/utils.d.ts +33 -0
  70. package/lib/otel/utils.js +181 -0
  71. package/lib/state/storage/IAMStorage.d.ts +2 -1
  72. package/lib/state/substates/CoreState.js +1 -0
  73. package/lib/utils/Jwt.d.ts +42 -0
  74. package/lib/utils/Jwt.js +44 -0
  75. package/lib/utils/index.d.ts +1 -0
  76. package/lib/utils/index.js +1 -0
  77. package/lib/views/iam/SignInSimple.d.ts +38 -0
  78. package/lib/views/iam/SignInSimple.js +80 -0
  79. package/lib/views/iam/index.d.ts +2 -0
  80. package/lib/views/iam/index.js +5 -0
  81. package/lib/views/iam-tokens/IAMTokenEdit.js +53 -4
  82. package/lib/views/iam-tokens/IAMTokens.js +65 -33
  83. package/lib/views/iam-tokens/Tokens.js +64 -32
  84. package/lib/views/index.d.ts +2 -1
  85. package/lib/views/index.js +2 -1
  86. package/lib/views/profile/UserBadge.d.ts +18 -0
  87. package/lib/views/profile/UserBadge.js +101 -0
  88. package/lib/views/profile/index.d.ts +2 -0
  89. package/lib/views/profile/index.js +5 -0
  90. package/lib/views/secrets/Secrets.js +1 -1
  91. package/package.json +27 -3
  92. package/lib/api/spacer/agentSpaces.d.ts +0 -193
  93. package/lib/api/spacer/agentSpaces.js +0 -127
  94. package/lib/theme/DatalayerTheme.d.ts +0 -52
  95. package/lib/theme/DatalayerTheme.js +0 -228
  96. package/lib/theme/DatalayerThemeProvider.d.ts +0 -29
  97. package/lib/theme/DatalayerThemeProvider.js +0 -54
  98. package/lib/theme/Palette.d.ts +0 -4
  99. package/lib/theme/Palette.js +0 -10
  100. package/lib/theme/index.d.ts +0 -4
  101. package/lib/theme/index.js +0 -8
  102. package/lib/theme/useSystemColorMode.d.ts +0 -9
  103. package/lib/theme/useSystemColorMode.js +0 -26
@@ -0,0 +1,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>;
@@ -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>;