@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,199 @@
|
|
|
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
|
+
* OtelTracesList – Tabular list of spans with Time / Message / Scope / Duration
|
|
8
|
+
* columns, using Primer React components for consistent theming.
|
|
9
|
+
*
|
|
10
|
+
* Spans that share the same trace_id are grouped into collapsible trees:
|
|
11
|
+
* only root spans are shown initially; clicking a row with children
|
|
12
|
+
* expands / collapses the nested child rows (shown indented).
|
|
13
|
+
*
|
|
14
|
+
* @module otel/OtelTracesList
|
|
15
|
+
*/
|
|
16
|
+
import { useMemo, useCallback, useState } from 'react';
|
|
17
|
+
import { Box, Text, Label, Spinner } from '@primer/react';
|
|
18
|
+
import { Blankslate } from '@primer/react/experimental';
|
|
19
|
+
import { TelescopeIcon, ChevronRightIcon, ChevronDownIcon, } from '@primer/octicons-react';
|
|
20
|
+
import { formatDuration, formatTime, buildSpanTree } from '../utils';
|
|
21
|
+
const GRID_COLS = '140px 1fr 160px 90px';
|
|
22
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
23
|
+
/** Extract gen_ai token counts from span attributes. */
|
|
24
|
+
function getTokenUsage(span) {
|
|
25
|
+
const attrs = span.attributes;
|
|
26
|
+
if (!attrs)
|
|
27
|
+
return null;
|
|
28
|
+
const input = attrs['gen_ai.usage.input_tokens'] != null
|
|
29
|
+
? Number(attrs['gen_ai.usage.input_tokens'])
|
|
30
|
+
: undefined;
|
|
31
|
+
const output = attrs['gen_ai.usage.output_tokens'] != null
|
|
32
|
+
? Number(attrs['gen_ai.usage.output_tokens'])
|
|
33
|
+
: undefined;
|
|
34
|
+
if (input == null && output == null)
|
|
35
|
+
return null;
|
|
36
|
+
return { input, output };
|
|
37
|
+
}
|
|
38
|
+
export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }) => {
|
|
39
|
+
// Set of expanded span_ids (show their children)
|
|
40
|
+
const [expanded, setExpanded] = useState(new Set());
|
|
41
|
+
// Build the span tree from flat input
|
|
42
|
+
const roots = useMemo(() => buildSpanTree(spans), [spans]);
|
|
43
|
+
// Flatten visible rows based on which nodes are expanded
|
|
44
|
+
const visibleRows = useMemo(() => {
|
|
45
|
+
const rows = [];
|
|
46
|
+
function walk(node, depth) {
|
|
47
|
+
const children = node.children ?? [];
|
|
48
|
+
rows.push({ span: node, depth, hasChildren: children.length > 0 });
|
|
49
|
+
if (expanded.has(node.span_id)) {
|
|
50
|
+
for (const child of children) {
|
|
51
|
+
walk(child, depth + 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const root of roots) {
|
|
56
|
+
walk(root, 0);
|
|
57
|
+
}
|
|
58
|
+
return rows;
|
|
59
|
+
}, [roots, expanded]);
|
|
60
|
+
const toggleExpand = useCallback((spanId) => {
|
|
61
|
+
setExpanded(prev => {
|
|
62
|
+
const next = new Set(prev);
|
|
63
|
+
if (next.has(spanId)) {
|
|
64
|
+
next.delete(spanId);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
next.add(spanId);
|
|
68
|
+
}
|
|
69
|
+
return next;
|
|
70
|
+
});
|
|
71
|
+
}, []);
|
|
72
|
+
if (loading) {
|
|
73
|
+
return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 5 }, children: _jsx(Spinner, { size: "medium" }) }));
|
|
74
|
+
}
|
|
75
|
+
if (spans.length === 0) {
|
|
76
|
+
return (_jsxs(Blankslate, { children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: 24 }) }), _jsx(Blankslate.Heading, { children: "No traces found" }), _jsx(Blankslate.Description, { children: "Send some telemetry data first." })] }));
|
|
77
|
+
}
|
|
78
|
+
return (_jsxs(Box, { sx: { width: '100%', flex: 1, minHeight: 0, overflow: 'auto' }, children: [_jsx(Box, { sx: {
|
|
79
|
+
display: 'grid',
|
|
80
|
+
gridTemplateColumns: GRID_COLS,
|
|
81
|
+
gap: 2,
|
|
82
|
+
px: 3,
|
|
83
|
+
position: 'sticky',
|
|
84
|
+
top: 0,
|
|
85
|
+
bg: 'canvas.subtle',
|
|
86
|
+
zIndex: 1,
|
|
87
|
+
borderBottom: '2px solid',
|
|
88
|
+
borderColor: 'border.default',
|
|
89
|
+
}, children: ['Time', 'Message', 'Scope', 'Duration'].map(h => (_jsx(Text, { sx: {
|
|
90
|
+
fontWeight: 'bold',
|
|
91
|
+
fontSize: 0,
|
|
92
|
+
color: 'fg.muted',
|
|
93
|
+
textTransform: 'uppercase',
|
|
94
|
+
letterSpacing: '0.05em',
|
|
95
|
+
py: 2,
|
|
96
|
+
...(h === 'Duration' ? { textAlign: 'right' } : {}),
|
|
97
|
+
}, children: h }, h))) }), visibleRows.map(({ span, depth, hasChildren }, idx) => {
|
|
98
|
+
const isSelected = selectedSpanId === span.span_id;
|
|
99
|
+
const isExpanded = expanded.has(span.span_id);
|
|
100
|
+
const indent = depth * 20;
|
|
101
|
+
return (_jsxs(Box, { onClick: () => {
|
|
102
|
+
if (hasChildren) {
|
|
103
|
+
toggleExpand(span.span_id);
|
|
104
|
+
}
|
|
105
|
+
onSelectSpan?.(span);
|
|
106
|
+
}, sx: {
|
|
107
|
+
display: 'grid',
|
|
108
|
+
gridTemplateColumns: GRID_COLS,
|
|
109
|
+
gap: 2,
|
|
110
|
+
px: 3,
|
|
111
|
+
py: '5px',
|
|
112
|
+
cursor: 'pointer',
|
|
113
|
+
bg: isSelected
|
|
114
|
+
? 'accent.subtle'
|
|
115
|
+
: depth > 0
|
|
116
|
+
? 'canvas.inset'
|
|
117
|
+
: 'canvas.default',
|
|
118
|
+
borderBottom: '1px solid',
|
|
119
|
+
borderColor: 'border.muted',
|
|
120
|
+
':hover': {
|
|
121
|
+
bg: isSelected ? 'accent.subtle' : 'canvas.subtle',
|
|
122
|
+
},
|
|
123
|
+
}, children: [_jsx(Text, { sx: {
|
|
124
|
+
fontSize: 1,
|
|
125
|
+
fontFamily: 'mono',
|
|
126
|
+
color: 'fg.muted',
|
|
127
|
+
whiteSpace: 'nowrap',
|
|
128
|
+
lineHeight: '22px',
|
|
129
|
+
}, children: formatTime(span.start_time) }), _jsxs(Box, { sx: {
|
|
130
|
+
display: 'flex',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
gap: 1,
|
|
133
|
+
overflow: 'hidden',
|
|
134
|
+
lineHeight: '22px',
|
|
135
|
+
pl: `${indent}px`,
|
|
136
|
+
}, children: [hasChildren ? (_jsx(Box, { sx: {
|
|
137
|
+
flexShrink: 0,
|
|
138
|
+
color: 'fg.muted',
|
|
139
|
+
display: 'flex',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
}, children: isExpanded ? (_jsx(ChevronDownIcon, { size: 14 })) : (_jsx(ChevronRightIcon, { size: 14 })) })) : depth > 0 ? (
|
|
142
|
+
/* Connector dash for child leaves */
|
|
143
|
+
_jsx(Box, { sx: {
|
|
144
|
+
flexShrink: 0,
|
|
145
|
+
width: '14px',
|
|
146
|
+
display: 'flex',
|
|
147
|
+
alignItems: 'center',
|
|
148
|
+
justifyContent: 'center',
|
|
149
|
+
color: 'fg.subtle',
|
|
150
|
+
fontSize: 0,
|
|
151
|
+
}, children: "\u2500" })) : null, _jsx(Text, { sx: {
|
|
152
|
+
fontSize: 1,
|
|
153
|
+
overflow: 'hidden',
|
|
154
|
+
textOverflow: 'ellipsis',
|
|
155
|
+
whiteSpace: 'nowrap',
|
|
156
|
+
}, title: span.span_name, children: span.span_name }), span.status_code === 'ERROR' && (_jsx(Label, { variant: "danger", size: "small", children: "ERROR" })), (() => {
|
|
157
|
+
const usage = getTokenUsage(span);
|
|
158
|
+
if (!usage)
|
|
159
|
+
return null;
|
|
160
|
+
return (_jsxs(Box, { sx: {
|
|
161
|
+
display: 'inline-flex',
|
|
162
|
+
alignItems: 'center',
|
|
163
|
+
gap: '2px',
|
|
164
|
+
border: '1px solid',
|
|
165
|
+
borderColor: 'border.default',
|
|
166
|
+
borderRadius: 2,
|
|
167
|
+
px: 1,
|
|
168
|
+
fontSize: '10px',
|
|
169
|
+
fontFamily: 'mono',
|
|
170
|
+
color: 'fg.muted',
|
|
171
|
+
flexShrink: 0,
|
|
172
|
+
lineHeight: '16px',
|
|
173
|
+
}, children: [usage.input != null && (_jsxs(Box, { sx: {
|
|
174
|
+
display: 'inline-flex',
|
|
175
|
+
alignItems: 'center',
|
|
176
|
+
gap: '1px',
|
|
177
|
+
}, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { sx: {
|
|
178
|
+
display: 'inline-flex',
|
|
179
|
+
alignItems: 'center',
|
|
180
|
+
gap: '1px',
|
|
181
|
+
}, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
|
|
182
|
+
})(), span.otel_scope_name && (_jsx(Label, { size: "small", variant: "secondary", sx: { flexShrink: 0, fontSize: '10px' }, children: span.otel_scope_name }))] }), _jsx(Text, { sx: {
|
|
183
|
+
fontSize: 1,
|
|
184
|
+
color: 'fg.muted',
|
|
185
|
+
overflow: 'hidden',
|
|
186
|
+
textOverflow: 'ellipsis',
|
|
187
|
+
whiteSpace: 'nowrap',
|
|
188
|
+
lineHeight: '22px',
|
|
189
|
+
}, title: `${span.service_name} / ${span.otel_scope_name ?? ''}`, children: span.service_name }), _jsx(Text, { sx: {
|
|
190
|
+
fontSize: 1,
|
|
191
|
+
fontFamily: 'mono',
|
|
192
|
+
textAlign: 'right',
|
|
193
|
+
lineHeight: '22px',
|
|
194
|
+
color: span.duration_ms != null && span.duration_ms > 1000
|
|
195
|
+
? 'danger.fg'
|
|
196
|
+
: 'fg.default',
|
|
197
|
+
}, children: formatDuration(span.duration_ms) })] }, `${span.trace_id}-${span.span_id}-${idx}`));
|
|
198
|
+
})] }));
|
|
199
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTEL view components.
|
|
3
|
+
*
|
|
4
|
+
* @module otel/views
|
|
5
|
+
*/
|
|
6
|
+
export { OtelLive } from './OtelLive';
|
|
7
|
+
export { OtelTracesList } from './OtelTracesList';
|
|
8
|
+
export { OtelLogsList } from './OtelLogsList';
|
|
9
|
+
export { OtelMetricsList } from './OtelMetricsList';
|
|
10
|
+
export { OtelMetricsChart } from './OtelMetricsChart';
|
|
11
|
+
export type { OtelMetricsChartProps } from './OtelMetricsChart';
|
|
12
|
+
export { OtelSearchBar } from './OtelSearchBar';
|
|
13
|
+
export { OtelSpanDetail } from './OtelSpanDetail';
|
|
14
|
+
export { OtelSpanTree } from './OtelSpanTree';
|
|
15
|
+
export { OtelTimeline } from './OtelTimeline';
|
|
16
|
+
export { OtelTimelineRangeSlider } from './OtelTimelineRangeSlider';
|
|
17
|
+
export { OtelSqlView } from './OtelSqlView';
|
|
18
|
+
export type { OtelSqlViewProps } from './OtelSqlView';
|
|
19
|
+
export { OtelSystemView } from './OtelSystemView';
|
|
20
|
+
export type { OtelSystemViewProps } from './OtelSystemView';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* OTEL view components.
|
|
7
|
+
*
|
|
8
|
+
* @module otel/views
|
|
9
|
+
*/
|
|
10
|
+
export { OtelLive } from './OtelLive';
|
|
11
|
+
export { OtelTracesList } from './OtelTracesList';
|
|
12
|
+
export { OtelLogsList } from './OtelLogsList';
|
|
13
|
+
export { OtelMetricsList } from './OtelMetricsList';
|
|
14
|
+
export { OtelMetricsChart } from './OtelMetricsChart';
|
|
15
|
+
export { OtelSearchBar } from './OtelSearchBar';
|
|
16
|
+
export { OtelSpanDetail } from './OtelSpanDetail';
|
|
17
|
+
export { OtelSpanTree } from './OtelSpanTree';
|
|
18
|
+
export { OtelTimeline } from './OtelTimeline';
|
|
19
|
+
export { OtelTimelineRangeSlider } from './OtelTimelineRangeSlider';
|
|
20
|
+
export { OtelSqlView } from './OtelSqlView';
|
|
21
|
+
export { OtelSystemView } from './OtelSystemView';
|
|
@@ -17,12 +17,13 @@ let initialConfiguration = {
|
|
|
17
17
|
loadConfigurationFromServer: true,
|
|
18
18
|
jupyterServerless: false,
|
|
19
19
|
iamRunUrl: 'https://prod1.datalayer.run',
|
|
20
|
-
runtimesRunUrl: 'https://
|
|
20
|
+
runtimesRunUrl: 'https://r1.datalayer.run',
|
|
21
21
|
libraryRunUrl: 'https://prod1.datalayer.run',
|
|
22
22
|
spacerRunUrl: 'https://prod1.datalayer.run',
|
|
23
23
|
aiagentsRunUrl: 'https://prod1.datalayer.run',
|
|
24
24
|
aiinferenceRunUrl: 'https://prod1.datalayer.run',
|
|
25
25
|
mcpserversRunUrl: 'https://prod1.datalayer.run',
|
|
26
|
+
otelRunUrl: 'https://prod1.datalayer.run',
|
|
26
27
|
growthRunUrl: 'https://prod1.datalayer.run',
|
|
27
28
|
inboundsRunUrl: 'https://prod1.datalayer.run',
|
|
28
29
|
successRunUrl: 'https://prod1.datalayer.run',
|
|
@@ -39,11 +40,11 @@ let initialConfiguration = {
|
|
|
39
40
|
logoUrl: 'https://assets.datalayer.tech/datalayer-25.svg',
|
|
40
41
|
logoSquareUrl: 'https://assets.datalayer.tech/datalayer-square.png',
|
|
41
42
|
copyright: '© 2025 Datalayer, Inc',
|
|
42
|
-
docsUrl: 'https://docs.datalayer.
|
|
43
|
-
supportUrl: 'https://datalayer.
|
|
44
|
-
termsUrl: 'https://datalayer.
|
|
45
|
-
pricingUrl: 'https://datalayer.
|
|
46
|
-
privacyUrl: 'https://datalayer.
|
|
43
|
+
docsUrl: 'https://docs.datalayer.ai',
|
|
44
|
+
supportUrl: 'https://datalayer.ai/support',
|
|
45
|
+
termsUrl: 'https://datalayer.ai/terms',
|
|
46
|
+
pricingUrl: 'https://datalayer.ai/pricing',
|
|
47
|
+
privacyUrl: 'https://datalayer.ai/privacy',
|
|
47
48
|
},
|
|
48
49
|
};
|
|
49
50
|
// Try loading initial state from datalayer-config-data element
|
package/lib/utils/Date.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
export declare const getRelativeTime: (d1: Date, d2?: Date) => string;
|
|
2
2
|
export declare const timeSince: (date: Date) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Format a timestamp into a compact relative string.
|
|
5
|
+
*
|
|
6
|
+
* Examples: "just now", "15m ago", "3h ago", "2d ago", "4w ago", "1y ago".
|
|
7
|
+
*/
|
|
8
|
+
export declare const formatRelativeTime: (value?: Date | string | number, now?: Date) => string | undefined;
|
package/lib/utils/Date.js
CHANGED
|
@@ -58,3 +58,40 @@ export const timeSince = (date) => {
|
|
|
58
58
|
const i = Math.floor(interval);
|
|
59
59
|
return i + ` second${p(i)}`;
|
|
60
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Format a timestamp into a compact relative string.
|
|
63
|
+
*
|
|
64
|
+
* Examples: "just now", "15m ago", "3h ago", "2d ago", "4w ago", "1y ago".
|
|
65
|
+
*/
|
|
66
|
+
export const formatRelativeTime = (value, now = new Date()) => {
|
|
67
|
+
if (value === undefined || value === null) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const date = value instanceof Date
|
|
71
|
+
? value
|
|
72
|
+
: typeof value === 'number'
|
|
73
|
+
? new Date(value)
|
|
74
|
+
: new Date(value);
|
|
75
|
+
const ts = date.getTime();
|
|
76
|
+
if (Number.isNaN(ts)) {
|
|
77
|
+
return typeof value === 'string' ? value : undefined;
|
|
78
|
+
}
|
|
79
|
+
const diffMs = Math.max(0, now.getTime() - ts);
|
|
80
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
81
|
+
if (seconds < 60)
|
|
82
|
+
return 'just now';
|
|
83
|
+
const minutes = Math.floor(seconds / 60);
|
|
84
|
+
if (minutes < 60)
|
|
85
|
+
return `${minutes}m ago`;
|
|
86
|
+
const hours = Math.floor(minutes / 60);
|
|
87
|
+
if (hours < 24)
|
|
88
|
+
return `${hours}h ago`;
|
|
89
|
+
const days = Math.floor(hours / 24);
|
|
90
|
+
if (days < 7)
|
|
91
|
+
return `${days}d ago`;
|
|
92
|
+
const weeks = Math.floor(days / 7);
|
|
93
|
+
if (weeks < 52)
|
|
94
|
+
return `${weeks}w ago`;
|
|
95
|
+
const years = Math.floor(days / 365);
|
|
96
|
+
return `${years}y ago`;
|
|
97
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight JWT payload utilities.
|
|
3
|
+
*
|
|
4
|
+
* Decodes a JWT without verification (client-side display only).
|
|
5
|
+
* Never use for security-critical checks.
|
|
6
|
+
*/
|
|
7
|
+
export interface DatalayerJwtUser {
|
|
8
|
+
id: string;
|
|
9
|
+
uid: string;
|
|
10
|
+
handle: string;
|
|
11
|
+
email: string;
|
|
12
|
+
firstName: string;
|
|
13
|
+
lastName: string;
|
|
14
|
+
avatarUrl: string;
|
|
15
|
+
roles: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Full Datalayer JWT payload shape. */
|
|
18
|
+
export interface DatalayerJwtPayload {
|
|
19
|
+
jti: string;
|
|
20
|
+
iss: string;
|
|
21
|
+
iat: number;
|
|
22
|
+
exp: number;
|
|
23
|
+
sub: string;
|
|
24
|
+
user: DatalayerJwtUser;
|
|
25
|
+
/** Legacy top-level roles array (some tokens). */
|
|
26
|
+
roles?: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decode the payload of a JWT without verifying the signature.
|
|
30
|
+
* Returns `null` on any error (malformed token, invalid base64, etc.).
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseJwtPayload<T = DatalayerJwtPayload>(token: string): T | null;
|
|
33
|
+
/**
|
|
34
|
+
* Extract the Datalayer user object from a JWT, returning `null` if the
|
|
35
|
+
* token is missing or cannot be decoded.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getDatalayerJwtUser(token: string | null | undefined): DatalayerJwtUser | null;
|
|
38
|
+
/**
|
|
39
|
+
* Format a human-readable display name from a JWT user.
|
|
40
|
+
* Prefers "FirstName LastName", falls back to handle.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getDatalayerDisplayName(user: DatalayerJwtUser | null | undefined, fallback?: string): string;
|
package/lib/utils/Jwt.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
// ── Utilities ─────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* Decode the payload of a JWT without verifying the signature.
|
|
8
|
+
* Returns `null` on any error (malformed token, invalid base64, etc.).
|
|
9
|
+
*/
|
|
10
|
+
export function parseJwtPayload(token) {
|
|
11
|
+
try {
|
|
12
|
+
const parts = token.split('.');
|
|
13
|
+
if (parts.length < 2)
|
|
14
|
+
return null;
|
|
15
|
+
// Base64Url → Base64 → JSON
|
|
16
|
+
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
17
|
+
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
|
|
18
|
+
const json = atob(padded);
|
|
19
|
+
return JSON.parse(json);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract the Datalayer user object from a JWT, returning `null` if the
|
|
27
|
+
* token is missing or cannot be decoded.
|
|
28
|
+
*/
|
|
29
|
+
export function getDatalayerJwtUser(token) {
|
|
30
|
+
if (!token)
|
|
31
|
+
return null;
|
|
32
|
+
const payload = parseJwtPayload(token);
|
|
33
|
+
return payload?.user ?? null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Format a human-readable display name from a JWT user.
|
|
37
|
+
* Prefers "FirstName LastName", falls back to handle.
|
|
38
|
+
*/
|
|
39
|
+
export function getDatalayerDisplayName(user, fallback = '') {
|
|
40
|
+
if (!user)
|
|
41
|
+
return fallback;
|
|
42
|
+
const full = `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim();
|
|
43
|
+
return full || user.handle || fallback;
|
|
44
|
+
}
|
package/lib/utils/index.d.ts
CHANGED
package/lib/utils/index.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SignInSimple – Generic handle + password sign-in form.
|
|
3
|
+
*
|
|
4
|
+
* Posts to `loginUrl` (default `/api/iam/v1/login`) with
|
|
5
|
+
* `{ handle, password }`, then calls `onSignIn(token, handle)` on
|
|
6
|
+
* success so the caller can persist credentials as needed.
|
|
7
|
+
*
|
|
8
|
+
* @module views/signin
|
|
9
|
+
*/
|
|
10
|
+
import React from 'react';
|
|
11
|
+
export interface SignInSimpleProps {
|
|
12
|
+
/**
|
|
13
|
+
* Called after a successful login with the JWT and the user handle.
|
|
14
|
+
* Typically used to store credentials in a Zustand / context store.
|
|
15
|
+
*/
|
|
16
|
+
onSignIn: (token: string, handle: string) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Called when the user authenticates with an API key.
|
|
19
|
+
* If not provided the "Sign in with API Key" button is hidden.
|
|
20
|
+
*/
|
|
21
|
+
onApiKeySignIn?: (apiKey: string) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Login endpoint. Defaults to `/api/iam/v1/login`.
|
|
24
|
+
* The endpoint must accept `POST { handle, password }` and return
|
|
25
|
+
* `{ success: boolean; token?: string; message?: string }`.
|
|
26
|
+
*/
|
|
27
|
+
loginUrl?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Optional heading text. Defaults to `"Datalayer OTEL"`.
|
|
30
|
+
*/
|
|
31
|
+
title?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Optional subtitle / description.
|
|
34
|
+
*/
|
|
35
|
+
description?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Leading icon element rendered next to the title.
|
|
38
|
+
* Defaults to `<TelescopeIcon size={24} />`.
|
|
39
|
+
*/
|
|
40
|
+
leadingIcon?: React.ReactNode;
|
|
41
|
+
}
|
|
42
|
+
export declare const SignInSimple: React.FC<SignInSimpleProps>;
|
|
43
|
+
export default SignInSimple;
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
* SignInSimple – Generic handle + password sign-in form.
|
|
8
|
+
*
|
|
9
|
+
* Posts to `loginUrl` (default `/api/iam/v1/login`) with
|
|
10
|
+
* `{ handle, password }`, then calls `onSignIn(token, handle)` on
|
|
11
|
+
* success so the caller can persist credentials as needed.
|
|
12
|
+
*
|
|
13
|
+
* @module views/signin
|
|
14
|
+
*/
|
|
15
|
+
import { useState, useCallback, useRef } from 'react';
|
|
16
|
+
import { Box, Button, FormControl, Heading, Text, Textarea, TextInput, } from '@primer/react';
|
|
17
|
+
import { Dialog } from '@primer/react/experimental';
|
|
18
|
+
import { EyeIcon, EyeClosedIcon, KeyIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
19
|
+
// ── Component ────────────────────────────────────────────────────────
|
|
20
|
+
export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl = '/api/iam/v1/login', title = 'Datalayer OTEL', description = 'Sign in to access the observability dashboard.', leadingIcon = _jsx(TelescopeIcon, { size: 24 }), }) => {
|
|
21
|
+
const [handle, setHandle] = useState('');
|
|
22
|
+
const [password, setPassword] = useState('');
|
|
23
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
const [error, setError] = useState(null);
|
|
26
|
+
// API Key dialog state
|
|
27
|
+
const [showApiKeyDialog, setShowApiKeyDialog] = useState(false);
|
|
28
|
+
const [apiKey, setApiKey] = useState('');
|
|
29
|
+
const apiKeyRef = useRef(null);
|
|
30
|
+
const submit = useCallback(async () => {
|
|
31
|
+
if (!handle || !password || loading)
|
|
32
|
+
return;
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const resp = await fetch(loginUrl, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({ handle, password }),
|
|
40
|
+
});
|
|
41
|
+
if (!resp.ok) {
|
|
42
|
+
throw new Error(`HTTP ${resp.status}`);
|
|
43
|
+
}
|
|
44
|
+
const data = await resp.json();
|
|
45
|
+
if (data.success && data.token) {
|
|
46
|
+
onSignIn(data.token, handle);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
setError(data.message || 'Invalid username or password.');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
setLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}, [handle, password, loading, loginUrl, onSignIn]);
|
|
59
|
+
const handleKeyDown = useCallback((e) => {
|
|
60
|
+
if (e.key === 'Enter')
|
|
61
|
+
submit();
|
|
62
|
+
}, [submit]);
|
|
63
|
+
return (_jsx(Box, { sx: {
|
|
64
|
+
display: 'flex',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
justifyContent: 'center',
|
|
67
|
+
height: '100vh',
|
|
68
|
+
bg: 'canvas.default',
|
|
69
|
+
color: 'fg.default',
|
|
70
|
+
}, children: _jsxs(Box, { sx: {
|
|
71
|
+
width: 360,
|
|
72
|
+
p: 4,
|
|
73
|
+
borderRadius: 2,
|
|
74
|
+
border: '1px solid',
|
|
75
|
+
borderColor: 'border.default',
|
|
76
|
+
bg: 'canvas.subtle',
|
|
77
|
+
}, children: [_jsxs(Box, { sx: {
|
|
78
|
+
display: 'flex',
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
gap: 2,
|
|
81
|
+
mb: 4,
|
|
82
|
+
justifyContent: 'center',
|
|
83
|
+
}, children: [leadingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: title })] }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mb: 3, textAlign: 'center' }, children: description }), _jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Username" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your username", value: handle, onChange: e => setHandle(e.target.value), onKeyDown: handleKeyDown })] }), _jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Password" }), _jsx(TextInput, { block: true, placeholder: "Your password", type: showPassword ? 'text' : 'password', value: password, onChange: e => setPassword(e.target.value), onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => setShowPassword(!showPassword), icon: showPassword ? EyeClosedIcon : EyeIcon, "aria-label": showPassword ? 'Hide password' : 'Show password', sx: { color: 'var(--fgColor-muted)' } }) })] }), error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 1, mb: 3, display: 'block' }, children: error })), _jsx(Button, { variant: "primary", block: true, disabled: loading || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign in' }), onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
84
|
+
display: 'flex',
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
gap: 2,
|
|
87
|
+
my: 3,
|
|
88
|
+
}, children: [_jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "or" }), _jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } })] }), _jsx(Button, { block: true, leadingVisual: KeyIcon, onClick: () => setShowApiKeyDialog(true), children: "Sign in with API Key" }), showApiKeyDialog && (_jsx(Dialog, { title: "Enter your API Key", onClose: () => {
|
|
89
|
+
setShowApiKeyDialog(false);
|
|
90
|
+
setApiKey('');
|
|
91
|
+
}, initialFocusRef: apiKeyRef, footerButtons: [
|
|
92
|
+
{
|
|
93
|
+
buttonType: 'default',
|
|
94
|
+
onClick: () => {
|
|
95
|
+
setShowApiKeyDialog(false);
|
|
96
|
+
setApiKey('');
|
|
97
|
+
},
|
|
98
|
+
content: 'Cancel',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
buttonType: 'primary',
|
|
102
|
+
onClick: () => {
|
|
103
|
+
if (apiKey.trim()) {
|
|
104
|
+
onApiKeySignIn(apiKey.trim());
|
|
105
|
+
setShowApiKeyDialog(false);
|
|
106
|
+
setApiKey('');
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
content: 'Authenticate',
|
|
110
|
+
},
|
|
111
|
+
], children: _jsx(Box, { as: "form", children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "API Key" }), _jsx(Textarea, { block: true, required: true, autoFocus: true, placeholder: "Paste your API key here", value: apiKey, onInput: (e) => setApiKey(e.target.value), ref: apiKeyRef })] }) }) }))] }))] }) }));
|
|
112
|
+
};
|
|
113
|
+
export default SignInSimple;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type IAMTokenEditProps = {
|
|
2
|
+
/** Route to navigate after delete. Defaults to '/settings/iam/tokens'. */
|
|
3
|
+
tokensListRoute?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const IAMTokenEdit: ({ tokensListRoute, }?: IAMTokenEditProps) => import("react/jsx-runtime").JSX.Element;
|
|
2
6
|
export default IAMTokenEdit;
|