@datalayer/core 1.0.3 → 1.0.12
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/lib/api/constants.d.ts +3 -0
- package/lib/api/constants.js +3 -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/hooks/index.d.ts +3 -0
- package/lib/hooks/index.js +3 -0
- package/lib/hooks/useCache.d.ts +5 -67
- package/lib/hooks/useCache.js +15 -213
- package/lib/hooks/useHighZIndexPortal.d.ts +4 -0
- package/lib/hooks/useHighZIndexPortal.js +36 -0
- package/lib/hooks/useKeyboardShortcuts.d.ts +23 -0
- package/lib/hooks/useKeyboardShortcuts.js +124 -0
- package/lib/hooks/useProjects.d.ts +1 -1
- 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.d.ts → hooks/index.d.ts} +15 -1
- package/lib/otel/{hooks.js → hooks/index.js} +58 -16
- package/lib/otel/index.d.ts +29 -20
- package/lib/otel/index.js +19 -15
- package/lib/otel/{OtelLive.d.ts → views/OtelLive.d.ts} +1 -1
- package/lib/otel/{OtelLive.js → views/OtelLive.js} +22 -4
- package/lib/otel/{OtelLogsList.d.ts → views/OtelLogsList.d.ts} +1 -1
- package/lib/otel/{OtelLogsList.js → views/OtelLogsList.js} +1 -1
- package/lib/otel/{OtelMetricsChart.d.ts → views/OtelMetricsChart.d.ts} +1 -1
- package/lib/otel/{OtelMetricsList.d.ts → views/OtelMetricsList.d.ts} +1 -1
- package/lib/otel/{OtelMetricsList.js → views/OtelMetricsList.js} +1 -1
- package/lib/otel/{OtelSearchBar.d.ts → views/OtelSearchBar.d.ts} +1 -1
- package/lib/otel/{OtelSpanDetail.d.ts → views/OtelSpanDetail.d.ts} +1 -1
- package/lib/otel/{OtelSpanDetail.js → views/OtelSpanDetail.js} +1 -1
- package/lib/otel/{OtelSpanTree.d.ts → views/OtelSpanTree.d.ts} +1 -1
- package/lib/otel/{OtelSpanTree.js → views/OtelSpanTree.js} +1 -1
- package/lib/otel/{OtelSqlView.js → views/OtelSqlView.js} +1 -1
- package/lib/otel/{OtelSystemView.js → views/OtelSystemView.js} +1 -1
- package/lib/otel/{OtelTimeline.d.ts → views/OtelTimeline.d.ts} +1 -1
- package/lib/otel/{OtelTimeline.js → views/OtelTimeline.js} +1 -1
- package/lib/otel/{OtelTimelineRangeSlider.d.ts → views/OtelTimelineRangeSlider.d.ts} +1 -1
- package/lib/otel/{OtelTracesList.d.ts → views/OtelTracesList.d.ts} +1 -1
- package/lib/otel/{OtelTracesList.js → views/OtelTracesList.js} +1 -1
- package/lib/otel/views/index.d.ts +20 -0
- package/lib/otel/views/index.js +21 -0
- package/lib/state/substates/CoreState.js +6 -6
- package/lib/utils/Date.d.ts +6 -0
- package/lib/utils/Date.js +37 -0
- package/lib/views/iam/SignInSimple.d.ts +5 -0
- package/lib/views/iam/SignInSimple.js +39 -6
- package/lib/views/iam-tokens/IAMTokenEdit.d.ts +5 -1
- package/lib/views/iam-tokens/IAMTokenEdit.js +3 -3
- 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 +4 -4
- package/lib/views/index.d.ts +1 -0
- package/lib/views/index.js +1 -0
- 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 +2 -0
- package/lib/views/profile/UserBadge.js +3 -3
- package/package.json +1 -26
- /package/lib/otel/{OtelMetricsChart.js → views/OtelMetricsChart.js} +0 -0
- /package/lib/otel/{OtelSearchBar.js → views/OtelSearchBar.js} +0 -0
- /package/lib/otel/{OtelSqlView.d.ts → views/OtelSqlView.d.ts} +0 -0
- /package/lib/otel/{OtelSystemView.d.ts → views/OtelSystemView.d.ts} +0 -0
- /package/lib/otel/{OtelTimelineRangeSlider.js → views/OtelTimelineRangeSlider.js} +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* OtelClient – Typed HTTP client for the Datalayer OTEL service.
|
|
7
|
+
*
|
|
8
|
+
* Reads the OTEL service URL from the Datalayer core configuration by default,
|
|
9
|
+
* so callers don't need to pass `baseUrl` unless they want to override it.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { createOtelClient } from '@datalayer/core/otel';
|
|
14
|
+
*
|
|
15
|
+
* const client = createOtelClient({ token: myJwt });
|
|
16
|
+
* const { data: spans } = await client.fetchTraces({ limit: 50 });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { coreStore } from '../../state/substates/CoreState';
|
|
20
|
+
// ── Fetch helper ────────────────────────────────────────────────────────────
|
|
21
|
+
async function otelFetch(url, token, options) {
|
|
22
|
+
const headers = {
|
|
23
|
+
Accept: 'application/json',
|
|
24
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
25
|
+
};
|
|
26
|
+
if (token) {
|
|
27
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
28
|
+
}
|
|
29
|
+
if (options?.body) {
|
|
30
|
+
headers['Content-Type'] = 'application/json';
|
|
31
|
+
}
|
|
32
|
+
const resp = await fetch(url, {
|
|
33
|
+
method: options?.method ?? 'GET',
|
|
34
|
+
headers,
|
|
35
|
+
body: options?.body != null ? JSON.stringify(options.body) : undefined,
|
|
36
|
+
});
|
|
37
|
+
if (!resp.ok) {
|
|
38
|
+
const text = await resp.text().catch(() => '');
|
|
39
|
+
throw new Error(`OTEL API error ${resp.status}: ${text}`);
|
|
40
|
+
}
|
|
41
|
+
return resp.json();
|
|
42
|
+
}
|
|
43
|
+
function parseMetricValue(raw) {
|
|
44
|
+
const candidates = [raw.value, raw.value_double, raw.value_int];
|
|
45
|
+
for (const candidate of candidates) {
|
|
46
|
+
if (typeof candidate === 'number' && Number.isFinite(candidate)) {
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
if (typeof candidate === 'string') {
|
|
50
|
+
const parsed = Number(candidate);
|
|
51
|
+
if (Number.isFinite(parsed)) {
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
function parseMetricTimestamp(raw) {
|
|
59
|
+
const candidate = raw.timestamp ??
|
|
60
|
+
raw.Timestamp ??
|
|
61
|
+
raw.timestamp_unix_nano ??
|
|
62
|
+
raw.start_time_unix_nano;
|
|
63
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
64
|
+
return candidate;
|
|
65
|
+
}
|
|
66
|
+
const numeric = Number(candidate);
|
|
67
|
+
if (!Number.isFinite(numeric)) {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
if (numeric > 1e15) {
|
|
71
|
+
return new Date(numeric / 1e6).toISOString();
|
|
72
|
+
}
|
|
73
|
+
if (numeric > 1e12) {
|
|
74
|
+
return new Date(numeric / 1e3).toISOString();
|
|
75
|
+
}
|
|
76
|
+
return new Date(numeric).toISOString();
|
|
77
|
+
}
|
|
78
|
+
function normalizeMetric(raw) {
|
|
79
|
+
return {
|
|
80
|
+
metric_name: String(raw.metric_name ?? raw.MetricName ?? raw.name ?? raw.Name ?? ''),
|
|
81
|
+
service_name: String(raw.service_name ?? raw.ServiceName ?? ''),
|
|
82
|
+
value: parseMetricValue(raw),
|
|
83
|
+
unit: raw.metric_unit != null
|
|
84
|
+
? String(raw.metric_unit)
|
|
85
|
+
: raw.MetricUnit != null
|
|
86
|
+
? String(raw.MetricUnit)
|
|
87
|
+
: raw.unit != null
|
|
88
|
+
? String(raw.unit)
|
|
89
|
+
: raw.Unit != null
|
|
90
|
+
? String(raw.Unit)
|
|
91
|
+
: undefined,
|
|
92
|
+
timestamp: parseMetricTimestamp(raw),
|
|
93
|
+
metric_type: raw.metric_type != null
|
|
94
|
+
? String(raw.metric_type)
|
|
95
|
+
: raw.MetricType != null
|
|
96
|
+
? String(raw.MetricType)
|
|
97
|
+
: undefined,
|
|
98
|
+
attributes: typeof raw.attributes === 'object' && raw.attributes !== null
|
|
99
|
+
? raw.attributes
|
|
100
|
+
: undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// ── OtelClient class ─────────────────────────────────────────────────────────
|
|
104
|
+
/**
|
|
105
|
+
* Typed HTTP client for all OTEL service endpoints.
|
|
106
|
+
*
|
|
107
|
+
* Construct via `createOtelClient()` or `new OtelClient(options)`.
|
|
108
|
+
*/
|
|
109
|
+
export class OtelClient {
|
|
110
|
+
baseUrl;
|
|
111
|
+
token;
|
|
112
|
+
constructor(options = {}) {
|
|
113
|
+
this.baseUrl =
|
|
114
|
+
options.baseUrl ?? coreStore.getState().configuration.otelRunUrl;
|
|
115
|
+
this.token = options.token;
|
|
116
|
+
}
|
|
117
|
+
// ── Traces ────────────────────────────────────────────────────────────────
|
|
118
|
+
/** List recent spans / traces. */
|
|
119
|
+
async fetchTraces(options = {}) {
|
|
120
|
+
const params = new URLSearchParams({ limit: String(options.limit ?? 50) });
|
|
121
|
+
if (options.serviceName)
|
|
122
|
+
params.set('service_name', options.serviceName);
|
|
123
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/?${params}`, this.token);
|
|
124
|
+
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
125
|
+
return { data: rows, count: rows.length };
|
|
126
|
+
}
|
|
127
|
+
/** Fetch all spans for a single trace by `traceId`. */
|
|
128
|
+
async fetchTrace(traceId) {
|
|
129
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/${traceId}`, this.token);
|
|
130
|
+
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
131
|
+
return { data: rows };
|
|
132
|
+
}
|
|
133
|
+
// ── Logs ──────────────────────────────────────────────────────────────────
|
|
134
|
+
/** List recent log records. */
|
|
135
|
+
async fetchLogs(options = {}) {
|
|
136
|
+
const params = new URLSearchParams({ limit: String(options.limit ?? 100) });
|
|
137
|
+
if (options.serviceName)
|
|
138
|
+
params.set('service_name', options.serviceName);
|
|
139
|
+
if (options.severity)
|
|
140
|
+
params.set('severity', options.severity);
|
|
141
|
+
if (options.traceId)
|
|
142
|
+
params.set('trace_id', options.traceId);
|
|
143
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/logs/?${params}`, this.token);
|
|
144
|
+
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
145
|
+
return { data: rows, count: rows.length };
|
|
146
|
+
}
|
|
147
|
+
// ── Metrics ───────────────────────────────────────────────────────────────
|
|
148
|
+
/** List recent metric data points. */
|
|
149
|
+
async fetchMetrics(options = {}) {
|
|
150
|
+
const params = new URLSearchParams({ limit: String(options.limit ?? 50) });
|
|
151
|
+
if (options.serviceName)
|
|
152
|
+
params.set('service_name', options.serviceName);
|
|
153
|
+
if (options.metricName)
|
|
154
|
+
params.set('metric_name', options.metricName);
|
|
155
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/metrics/?${params}`, this.token);
|
|
156
|
+
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
157
|
+
const normalized = Array.isArray(rows)
|
|
158
|
+
? rows.map(row => normalizeMetric(row))
|
|
159
|
+
: [];
|
|
160
|
+
return { data: normalized, count: normalized.length };
|
|
161
|
+
}
|
|
162
|
+
/** Sum all values for one metric name, with optional service filter. */
|
|
163
|
+
async fetchMetricTotal(metricName, options = {}) {
|
|
164
|
+
const { serviceName, limit = 500, fallbackWithoutService = true } = options;
|
|
165
|
+
const filtered = await this.fetchMetrics({
|
|
166
|
+
metricName,
|
|
167
|
+
serviceName,
|
|
168
|
+
limit,
|
|
169
|
+
});
|
|
170
|
+
const filteredTotal = filtered.data.reduce((sum, row) => sum + Number(row.value || 0), 0);
|
|
171
|
+
if (filteredTotal > 0 || !serviceName || !fallbackWithoutService) {
|
|
172
|
+
return filteredTotal;
|
|
173
|
+
}
|
|
174
|
+
const unfiltered = await this.fetchMetrics({ metricName, limit });
|
|
175
|
+
return unfiltered.data.reduce((sum, row) => sum + Number(row.value || 0), 0);
|
|
176
|
+
}
|
|
177
|
+
// ── Services ──────────────────────────────────────────────────────────────
|
|
178
|
+
/** List known service names observed in traces. */
|
|
179
|
+
async fetchServices() {
|
|
180
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/services/list`, this.token);
|
|
181
|
+
if (Array.isArray(resp))
|
|
182
|
+
return resp;
|
|
183
|
+
const byServices = resp.services;
|
|
184
|
+
if (Array.isArray(byServices))
|
|
185
|
+
return byServices;
|
|
186
|
+
const byData = resp.data;
|
|
187
|
+
if (Array.isArray(byData)) {
|
|
188
|
+
return byData.map((r) => String(r.service_name ?? r.name ?? ''));
|
|
189
|
+
}
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
// ── Stats ─────────────────────────────────────────────────────────────────
|
|
193
|
+
/** Fetch storage / ingestion statistics. */
|
|
194
|
+
async fetchStats() {
|
|
195
|
+
return otelFetch(`${this.baseUrl}/api/otel/v1/stats/`, this.token);
|
|
196
|
+
}
|
|
197
|
+
// ── SQL query ─────────────────────────────────────────────────────────────
|
|
198
|
+
/**
|
|
199
|
+
* Execute an ad-hoc DataFusion SQL query against the OTEL service.
|
|
200
|
+
*
|
|
201
|
+
* Available tables: `spans`, `logs`, `metrics`.
|
|
202
|
+
*/
|
|
203
|
+
async executeQuery(sql) {
|
|
204
|
+
return otelFetch(`${this.baseUrl}/api/otel/v1/query/`, this.token, { method: 'POST', body: { sql } });
|
|
205
|
+
}
|
|
206
|
+
// ── System ────────────────────────────────────────────────────────────────
|
|
207
|
+
/**
|
|
208
|
+
* Fetch system statistics (platform_admin only).
|
|
209
|
+
*
|
|
210
|
+
* Returns process memory/CPU, disk usage, and per-table row counts.
|
|
211
|
+
*/
|
|
212
|
+
async fetchSystem() {
|
|
213
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/system/`, this.token);
|
|
214
|
+
return (resp.data ?? resp);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// ── Factory ──────────────────────────────────────────────────────────────────
|
|
218
|
+
/**
|
|
219
|
+
* Create a new `OtelClient`.
|
|
220
|
+
*
|
|
221
|
+
* The `baseUrl` defaults to `configuration.otelRunUrl` from the Datalayer core
|
|
222
|
+
* configuration store, so only pass it when you want to override the default.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* const client = createOtelClient({ token: jwt });
|
|
227
|
+
* const services = await client.fetchServices();
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
export function createOtelClient(options = {}) {
|
|
231
|
+
return new OtelClient(options);
|
|
232
|
+
}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import type { OtelSpan, OtelLog, OtelMetric, OtelQueryRow } from '
|
|
1
|
+
import type { OtelSpan, OtelLog, OtelMetric, OtelQueryRow } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Register a callback invoked whenever an OTEL API call receives a
|
|
4
|
+
* **401 Unauthorized** response. Typically used to clear auth state
|
|
5
|
+
* (i.e. log the user out) when the token has expired.
|
|
6
|
+
*
|
|
7
|
+
* Pass `null` to unregister.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { setOtelOnUnauthorized } from '@datalayer/core/lib/otel';
|
|
12
|
+
* setOtelOnUnauthorized(() => authStore.getState().clearAuth());
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function setOtelOnUnauthorized(cb: (() => void) | null): void;
|
|
2
16
|
/** Fetch a list of traces / spans from the OTEL service. */
|
|
3
17
|
export declare function useOtelTraces(options: {
|
|
4
18
|
token?: string;
|
|
@@ -8,9 +8,32 @@
|
|
|
8
8
|
* @module otel/hooks
|
|
9
9
|
*/
|
|
10
10
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
|
+
import { coreStore } from '../../state/substates/CoreState';
|
|
12
|
+
// ── Global 401 handler ──────────────────────────────────────────────
|
|
13
|
+
let _onUnauthorized = null;
|
|
14
|
+
/**
|
|
15
|
+
* Register a callback invoked whenever an OTEL API call receives a
|
|
16
|
+
* **401 Unauthorized** response. Typically used to clear auth state
|
|
17
|
+
* (i.e. log the user out) when the token has expired.
|
|
18
|
+
*
|
|
19
|
+
* Pass `null` to unregister.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { setOtelOnUnauthorized } from '@datalayer/core/lib/otel';
|
|
24
|
+
* setOtelOnUnauthorized(() => authStore.getState().clearAuth());
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function setOtelOnUnauthorized(cb) {
|
|
28
|
+
_onUnauthorized = cb;
|
|
29
|
+
}
|
|
11
30
|
/**
|
|
12
31
|
* Lightweight fetch helper for OTEL API calls.
|
|
13
32
|
* Uses plain `fetch` to avoid pulling in `@jupyterlab/coreutils` / axios.
|
|
33
|
+
*
|
|
34
|
+
* If the response is **401 Unauthorized** and a global `onUnauthorized`
|
|
35
|
+
* handler has been registered via {@link setOtelOnUnauthorized}, the
|
|
36
|
+
* handler is called before the error is thrown.
|
|
14
37
|
*/
|
|
15
38
|
async function otelFetch(url, token, options) {
|
|
16
39
|
const headers = {
|
|
@@ -33,6 +56,9 @@ async function otelFetch(url, token, options) {
|
|
|
33
56
|
: undefined,
|
|
34
57
|
});
|
|
35
58
|
if (!res.ok) {
|
|
59
|
+
if (res.status === 401 && _onUnauthorized) {
|
|
60
|
+
_onUnauthorized();
|
|
61
|
+
}
|
|
36
62
|
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
37
63
|
}
|
|
38
64
|
return res.json();
|
|
@@ -162,7 +188,7 @@ function normalizeMetric(raw) {
|
|
|
162
188
|
// ── useOtelTraces ───────────────────────────────────────────────────
|
|
163
189
|
/** Fetch a list of traces / spans from the OTEL service. */
|
|
164
190
|
export function useOtelTraces(options) {
|
|
165
|
-
const { token, baseUrl =
|
|
191
|
+
const { token, baseUrl = coreStore.getState().configuration.otelRunUrl, serviceName, limit = 50, autoRefreshMs, } = options;
|
|
166
192
|
const [traces, setTraces] = useState([]);
|
|
167
193
|
const [loading, setLoading] = useState(true);
|
|
168
194
|
const [error, setError] = useState(null);
|
|
@@ -219,7 +245,7 @@ export function useOtelTrace(options) {
|
|
|
219
245
|
// ── useOtelLogs ─────────────────────────────────────────────────────
|
|
220
246
|
/** Fetch log records from the OTEL service. */
|
|
221
247
|
export function useOtelLogs(options) {
|
|
222
|
-
const { token, baseUrl =
|
|
248
|
+
const { token, baseUrl = coreStore.getState().configuration.otelRunUrl, serviceName, severity, traceId, limit = 100, autoRefreshMs, } = options;
|
|
223
249
|
const [logs, setLogs] = useState([]);
|
|
224
250
|
const [loading, setLoading] = useState(true);
|
|
225
251
|
const [error, setError] = useState(null);
|
|
@@ -256,7 +282,7 @@ export function useOtelLogs(options) {
|
|
|
256
282
|
// ── useOtelMetrics ──────────────────────────────────────────────────
|
|
257
283
|
/** Fetch metrics from the OTEL service. */
|
|
258
284
|
export function useOtelMetrics(options) {
|
|
259
|
-
const { token, baseUrl =
|
|
285
|
+
const { token, baseUrl = coreStore.getState().configuration.otelRunUrl, serviceName, metricName, limit = 50, autoRefreshMs, } = options;
|
|
260
286
|
const [metrics, setMetrics] = useState([]);
|
|
261
287
|
const [loading, setLoading] = useState(true);
|
|
262
288
|
const [error, setError] = useState(null);
|
|
@@ -291,22 +317,38 @@ export function useOtelMetrics(options) {
|
|
|
291
317
|
// ── useOtelServices ─────────────────────────────────────────────────
|
|
292
318
|
/** Fetch list of observed service names. */
|
|
293
319
|
export function useOtelServices(options) {
|
|
294
|
-
const { token, baseUrl =
|
|
320
|
+
const { token, baseUrl = coreStore.getState().configuration.otelRunUrl } = options;
|
|
295
321
|
const [services, setServices] = useState([]);
|
|
296
322
|
const [loading, setLoading] = useState(true);
|
|
297
323
|
useEffect(() => {
|
|
298
|
-
otelFetch(`${baseUrl}/api/otel/v1/traces/services/list
|
|
324
|
+
otelFetch(`${baseUrl}/api/otel/v1/traces/services/list/`, token)
|
|
299
325
|
.then(data => {
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
326
|
+
// Accept multiple backend response shapes and normalize to unique names.
|
|
327
|
+
// Examples:
|
|
328
|
+
// { services: ["a","b"] }
|
|
329
|
+
// { data: [{ service_name: "a" }] }
|
|
330
|
+
// { data: [{ service: "a" }] }
|
|
331
|
+
// ["a", "b"]
|
|
332
|
+
// [{ serviceName: "a" }]
|
|
333
|
+
const raw = data.services ?? data.data ?? data;
|
|
334
|
+
const rows = Array.isArray(raw) ? raw : [];
|
|
335
|
+
const names = rows
|
|
336
|
+
.map(row => {
|
|
337
|
+
if (typeof row === 'string') {
|
|
338
|
+
return row.trim();
|
|
339
|
+
}
|
|
340
|
+
if (row && typeof row === 'object') {
|
|
341
|
+
const record = row;
|
|
342
|
+
const value = record.service_name ??
|
|
343
|
+
record.serviceName ??
|
|
344
|
+
record.service ??
|
|
345
|
+
record.name;
|
|
346
|
+
return value == null ? '' : String(value).trim();
|
|
347
|
+
}
|
|
348
|
+
return '';
|
|
349
|
+
})
|
|
350
|
+
.filter(Boolean);
|
|
351
|
+
setServices(Array.from(new Set(names)).sort((a, b) => a.localeCompare(b)));
|
|
310
352
|
})
|
|
311
353
|
.catch(() => { })
|
|
312
354
|
.finally(() => setLoading(false));
|
|
@@ -394,7 +436,7 @@ export function useOtelQuery(options) {
|
|
|
394
436
|
* @returns `{ connected, error, close }` – reactive connection state.
|
|
395
437
|
*/
|
|
396
438
|
export function useOtelWebSocket(options) {
|
|
397
|
-
const { baseUrl =
|
|
439
|
+
const { baseUrl = coreStore.getState().configuration.otelRunUrl, token, autoReconnect = true, reconnectDelayMs = 3000, callbacks, } = options;
|
|
398
440
|
const [connected, setConnected] = useState(false);
|
|
399
441
|
const [error, setError] = useState(null);
|
|
400
442
|
const wsRef = useRef(null);
|
package/lib/otel/index.d.ts
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OTEL React components for visualizing OpenTelemetry
|
|
3
|
-
* (traces, logs, metrics) in a Logfire-inspired Live view.
|
|
2
|
+
* OTEL React components, hooks, and client for visualizing OpenTelemetry
|
|
3
|
+
* signals (traces, logs, metrics) in a Logfire-inspired Live view.
|
|
4
|
+
*
|
|
5
|
+
* Structure:
|
|
6
|
+
* - `types` – shared TypeScript types
|
|
7
|
+
* - `utils` – pure helpers (formatDuration, buildSpanTree, …)
|
|
8
|
+
* - `hooks/` – React hooks that read from Datalayer config by default
|
|
9
|
+
* - `client/`– Typed HTTP client (non-React, reads Datalayer config by default)
|
|
10
|
+
* - `views/` – React view components
|
|
4
11
|
*
|
|
5
12
|
* @module otel
|
|
6
13
|
*/
|
|
7
14
|
export type { OtelSpan, OtelLog, OtelMetric, OtelSpanEvent, OtelSpanLink, SignalType, OtelLiveProps, OtelTracesListProps, OtelSpanDetailProps, OtelTimelineProps, OtelLogsListProps, OtelMetricsListProps, OtelSpanTreeProps, OtelSearchBarProps, OtelTimelineRangeSliderProps, OtelQueryRow, OtelQueryResult, } from './types';
|
|
8
|
-
export {
|
|
9
|
-
export
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export
|
|
23
|
-
export {
|
|
24
|
-
export
|
|
25
|
-
export type {
|
|
15
|
+
export { toMs, formatDuration, formatTime, serviceColor, kindColor, severityColor, severityVariant, buildSpanTree, flattenSpanTree, } from './utils';
|
|
16
|
+
export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks';
|
|
17
|
+
export type { OtelWsMessage, OtelWsCallbacks, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './hooks';
|
|
18
|
+
export { OtelClient, createOtelClient } from './client';
|
|
19
|
+
export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, } from './client';
|
|
20
|
+
export { OtelLive } from './views';
|
|
21
|
+
export { OtelTracesList } from './views';
|
|
22
|
+
export { OtelSpanDetail } from './views';
|
|
23
|
+
export { OtelSpanTree } from './views';
|
|
24
|
+
export { OtelLogsList } from './views';
|
|
25
|
+
export { OtelSearchBar } from './views';
|
|
26
|
+
export { OtelMetricsList } from './views';
|
|
27
|
+
export { OtelMetricsChart } from './views';
|
|
28
|
+
export type { OtelMetricsChartProps } from './views';
|
|
29
|
+
export { OtelTimeline } from './views';
|
|
30
|
+
export { OtelTimelineRangeSlider } from './views';
|
|
31
|
+
export { OtelSqlView } from './views';
|
|
32
|
+
export type { OtelSqlViewProps } from './views';
|
|
33
|
+
export { OtelSystemView } from './views';
|
|
34
|
+
export type { OtelSystemViewProps } from './views';
|
package/lib/otel/index.js
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
-
//
|
|
6
|
-
export {
|
|
7
|
-
//
|
|
8
|
-
export {
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
5
|
+
// ── Utils ─────────────────────────────────────────────────────────────────────
|
|
6
|
+
export { toMs, formatDuration, formatTime, serviceColor, kindColor, severityColor, severityVariant, buildSpanTree, flattenSpanTree, } from './utils';
|
|
7
|
+
// ── Hooks ─────────────────────────────────────────────────────────────────────
|
|
8
|
+
export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks';
|
|
9
|
+
// ── Client ────────────────────────────────────────────────────────────────────
|
|
10
|
+
export { OtelClient, createOtelClient } from './client';
|
|
11
|
+
// ── Views ─────────────────────────────────────────────────────────────────────
|
|
12
|
+
export { OtelLive } from './views';
|
|
13
|
+
export { OtelTracesList } from './views';
|
|
14
|
+
export { OtelSpanDetail } from './views';
|
|
15
|
+
export { OtelSpanTree } from './views';
|
|
16
|
+
export { OtelLogsList } from './views';
|
|
17
|
+
export { OtelSearchBar } from './views';
|
|
18
|
+
export { OtelMetricsList } from './views';
|
|
19
|
+
export { OtelMetricsChart } from './views';
|
|
20
|
+
export { OtelTimeline } from './views';
|
|
21
|
+
export { OtelTimelineRangeSlider } from './views';
|
|
22
|
+
export { OtelSqlView } from './views';
|
|
23
|
+
export { OtelSystemView } from './views';
|
|
@@ -15,8 +15,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
import { useState, useCallback, useMemo, useEffect, useRef, } from 'react';
|
|
16
16
|
import { Box, Text, Button, Label } from '@primer/react';
|
|
17
17
|
import { GitBranchIcon, ClockIcon } from '@primer/octicons-react';
|
|
18
|
-
import { coreStore } from '
|
|
19
|
-
import { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelWebSocket, } from '
|
|
18
|
+
import { coreStore } from '../../state/substates/CoreState';
|
|
19
|
+
import { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelWebSocket, } from '../hooks';
|
|
20
20
|
import { OtelSearchBar } from './OtelSearchBar';
|
|
21
21
|
import { OtelTimelineRangeSlider } from './OtelTimelineRangeSlider';
|
|
22
22
|
import { OtelTracesList } from './OtelTracesList';
|
|
@@ -25,7 +25,7 @@ import { OtelMetricsList } from './OtelMetricsList';
|
|
|
25
25
|
import { OtelSpanDetail } from './OtelSpanDetail';
|
|
26
26
|
import { OtelTimeline } from './OtelTimeline';
|
|
27
27
|
import { OtelSpanTree } from './OtelSpanTree';
|
|
28
|
-
import { buildSpanTree } from '
|
|
28
|
+
import { buildSpanTree } from '../utils';
|
|
29
29
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
30
30
|
const BOTTOM_PANE_VIEWS = ['timeline', 'tree'];
|
|
31
31
|
const HISTOGRAM_BUCKETS = 60;
|
|
@@ -116,6 +116,24 @@ export const OtelLive = ({ baseUrl = coreStore.getState().configuration.otelRunU
|
|
|
116
116
|
autoRefreshMs,
|
|
117
117
|
});
|
|
118
118
|
const { services } = useOtelServices({ baseUrl, token });
|
|
119
|
+
const effectiveServices = useMemo(() => {
|
|
120
|
+
const fromApi = (services ?? []).map(s => s.trim()).filter(Boolean);
|
|
121
|
+
const fromTraces = (traces ?? [])
|
|
122
|
+
.map(s => (s.service_name ?? '').trim())
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
const fromLogs = (logs ?? [])
|
|
125
|
+
.map(l => (l.service_name ?? '').trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
const fromMetrics = (metrics ?? [])
|
|
128
|
+
.map(m => (m.service_name ?? '').trim())
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
const fromSignal = signal === 'traces'
|
|
131
|
+
? fromTraces
|
|
132
|
+
: signal === 'logs'
|
|
133
|
+
? fromLogs
|
|
134
|
+
: fromMetrics;
|
|
135
|
+
return Array.from(new Set([...fromApi, ...fromSignal])).sort((a, b) => a.localeCompare(b));
|
|
136
|
+
}, [services, traces, logs, metrics, signal]);
|
|
119
137
|
// ── WebSocket live updates ──
|
|
120
138
|
// When a WS message arrives for a signal, refetch the corresponding hook
|
|
121
139
|
// so the data stays fresh without polling.
|
|
@@ -281,7 +299,7 @@ export const OtelLive = ({ baseUrl = coreStore.getState().configuration.otelRunU
|
|
|
281
299
|
borderColor: 'border.default',
|
|
282
300
|
borderRadius: 2,
|
|
283
301
|
overflow: 'hidden',
|
|
284
|
-
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0 }, children: [_jsx(Box, { sx: { flex: 1 }, children: _jsx(OtelSearchBar, { signal: signal, onSignalChange: setSignal, services:
|
|
302
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0 }, children: [_jsx(Box, { sx: { flex: 1 }, children: _jsx(OtelSearchBar, { signal: signal, onSignalChange: setSignal, services: effectiveServices, selectedService: service, onServiceChange: setService, query: query, onQueryChange: setQuery, onRefresh: handleRefresh, loading: signal === 'traces'
|
|
285
303
|
? tracesLoading
|
|
286
304
|
: signal === 'logs'
|
|
287
305
|
? logsLoading
|
|
@@ -15,7 +15,7 @@ import React, { useState } from 'react';
|
|
|
15
15
|
import { Box, Text, Label, Spinner } from '@primer/react';
|
|
16
16
|
import { Blankslate } from '@primer/react/experimental';
|
|
17
17
|
import { LogIcon } from '@primer/octicons-react';
|
|
18
|
-
import { formatTime, severityVariant } from '
|
|
18
|
+
import { formatTime, severityVariant } from '../utils';
|
|
19
19
|
// ── helpers ─────────────────────────────────────────────────────────
|
|
20
20
|
/** Severity badge using Primer Label, centered in its grid cell. */
|
|
21
21
|
const Severity = ({ text }) => (_jsx(Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx(Label, { size: "small", variant: severityVariant(text), children: text }) }));
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @module otel/OtelMetricsChart
|
|
13
13
|
*/
|
|
14
14
|
import React from 'react';
|
|
15
|
-
import type { OtelMetric } from '
|
|
15
|
+
import type { OtelMetric } from '../types';
|
|
16
16
|
export interface OtelMetricsChartProps {
|
|
17
17
|
metrics: OtelMetric[];
|
|
18
18
|
/** Height per chart panel in px. Default 240. */
|
|
@@ -15,7 +15,7 @@ import { useMemo, useState } from 'react';
|
|
|
15
15
|
import { Box, Text, Label, Spinner, CounterLabel, SegmentedControl, } from '@primer/react';
|
|
16
16
|
import { Blankslate } from '@primer/react/experimental';
|
|
17
17
|
import { MeterIcon, ChevronDownIcon, ChevronRightIcon, GraphIcon, TableIcon, } from '@primer/octicons-react';
|
|
18
|
-
import { formatTime } from '
|
|
18
|
+
import { formatTime } from '../utils';
|
|
19
19
|
import { OtelMetricsChart } from './OtelMetricsChart';
|
|
20
20
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
21
21
|
/** Group metrics by metric_name. */
|
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
import { useState } from 'react';
|
|
15
15
|
import { Box, Text, IconButton, UnderlineNav, CounterLabel, Label, } from '@primer/react';
|
|
16
16
|
import { XIcon, ChevronDownIcon, ChevronRightIcon, } from '@primer/octicons-react';
|
|
17
|
-
import { formatDuration, buildSpanTree } from '
|
|
17
|
+
import { formatDuration, buildSpanTree } from '../utils';
|
|
18
18
|
import { OtelSpanTree } from './OtelSpanTree';
|
|
19
19
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
20
20
|
/** Single key–value metadata row. */
|