@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.
- package/README.md +1 -1
- package/lib/api/constants.d.ts +3 -0
- package/lib/api/constants.js +3 -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/spacer/index.d.ts +1 -2
- package/lib/api/spacer/index.js +1 -2
- package/lib/components/avatars/BoringAvatar.d.ts +3 -1
- package/lib/components/avatars/BoringAvatar.js +15 -14
- package/lib/components/avatars/BoringAvatar.stories.d.ts +2 -1
- package/lib/components/storage/ContentsBrowser.d.ts +6 -0
- package/lib/components/storage/ContentsBrowser.js +7 -8
- package/lib/config/Configuration.d.ts +4 -0
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +2 -0
- package/lib/hooks/useCache.d.ts +16 -40
- package/lib/hooks/useCache.js +28 -233
- package/lib/hooks/useProjectStore.d.ts +58 -0
- package/lib/hooks/useProjectStore.js +64 -0
- package/lib/hooks/useProjects.d.ts +590 -0
- package/lib/hooks/useProjects.js +166 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +4 -2
- package/lib/models/Page.d.ts +2 -0
- package/lib/otel/OtelLive.d.ts +12 -0
- package/lib/otel/OtelLive.js +354 -0
- package/lib/otel/OtelLogsList.d.ts +11 -0
- package/lib/otel/OtelLogsList.js +137 -0
- package/lib/otel/OtelMetricsChart.d.ts +22 -0
- package/lib/otel/OtelMetricsChart.js +300 -0
- package/lib/otel/OtelMetricsList.d.ts +15 -0
- package/lib/otel/OtelMetricsList.js +213 -0
- package/lib/otel/OtelSearchBar.d.ts +11 -0
- package/lib/otel/OtelSearchBar.js +22 -0
- package/lib/otel/OtelSpanDetail.d.ts +11 -0
- package/lib/otel/OtelSpanDetail.js +172 -0
- package/lib/otel/OtelSpanTree.d.ts +11 -0
- package/lib/otel/OtelSpanTree.js +176 -0
- package/lib/otel/OtelSqlView.d.ts +16 -0
- package/lib/otel/OtelSqlView.js +239 -0
- package/lib/otel/OtelSystemView.d.ts +15 -0
- package/lib/otel/OtelSystemView.js +75 -0
- package/lib/otel/OtelTimeline.d.ts +11 -0
- package/lib/otel/OtelTimeline.js +101 -0
- package/lib/otel/OtelTimelineRangeSlider.d.ts +16 -0
- package/lib/otel/OtelTimelineRangeSlider.js +338 -0
- package/lib/otel/OtelTracesList.d.ts +13 -0
- package/lib/otel/OtelTracesList.js +199 -0
- package/lib/otel/hooks.d.ts +172 -0
- package/lib/otel/hooks.js +490 -0
- package/lib/otel/index.d.ts +25 -0
- package/lib/otel/index.js +19 -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/state/storage/IAMStorage.d.ts +2 -1
- package/lib/state/substates/CoreState.js +1 -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 +38 -0
- package/lib/views/iam/SignInSimple.js +80 -0
- package/lib/views/iam/index.d.ts +2 -0
- package/lib/views/iam/index.js +5 -0
- package/lib/views/iam-tokens/IAMTokenEdit.js +53 -4
- package/lib/views/iam-tokens/IAMTokens.js +65 -33
- package/lib/views/iam-tokens/Tokens.js +64 -32
- package/lib/views/index.d.ts +2 -1
- package/lib/views/index.js +2 -1
- package/lib/views/profile/UserBadge.d.ts +18 -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/lib/views/secrets/Secrets.js +1 -1
- package/package.json +27 -3
- package/lib/api/spacer/agentSpaces.d.ts +0 -193
- package/lib/api/spacer/agentSpaces.js +0 -127
- package/lib/theme/DatalayerTheme.d.ts +0 -52
- package/lib/theme/DatalayerTheme.js +0 -228
- package/lib/theme/DatalayerThemeProvider.d.ts +0 -29
- package/lib/theme/DatalayerThemeProvider.js +0 -54
- package/lib/theme/Palette.d.ts +0 -4
- package/lib/theme/Palette.js +0 -10
- package/lib/theme/index.d.ts +0 -4
- package/lib/theme/index.js +0 -8
- package/lib/theme/useSystemColorMode.d.ts +0 -9
- package/lib/theme/useSystemColorMode.js +0 -26
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { OtelSpan, OtelLog, OtelMetric, OtelQueryRow } from './types';
|
|
2
|
+
/** Fetch a list of traces / spans from the OTEL service. */
|
|
3
|
+
export declare function useOtelTraces(options: {
|
|
4
|
+
token?: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
serviceName?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
autoRefreshMs?: number;
|
|
9
|
+
}): {
|
|
10
|
+
traces: OtelSpan[];
|
|
11
|
+
loading: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
refetch: () => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
/** Fetch all spans for a single trace. */
|
|
16
|
+
export declare function useOtelTrace(options: {
|
|
17
|
+
traceId: string | null;
|
|
18
|
+
token?: string;
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
}): {
|
|
21
|
+
spans: OtelSpan[];
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: string | null;
|
|
24
|
+
};
|
|
25
|
+
/** Fetch log records from the OTEL service. */
|
|
26
|
+
export declare function useOtelLogs(options: {
|
|
27
|
+
token?: string;
|
|
28
|
+
baseUrl?: string;
|
|
29
|
+
serviceName?: string;
|
|
30
|
+
severity?: string;
|
|
31
|
+
traceId?: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
autoRefreshMs?: number;
|
|
34
|
+
}): {
|
|
35
|
+
logs: OtelLog[];
|
|
36
|
+
loading: boolean;
|
|
37
|
+
error: string | null;
|
|
38
|
+
refetch: () => Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
/** Fetch metrics from the OTEL service. */
|
|
41
|
+
export declare function useOtelMetrics(options: {
|
|
42
|
+
token?: string;
|
|
43
|
+
baseUrl?: string;
|
|
44
|
+
serviceName?: string;
|
|
45
|
+
metricName?: string;
|
|
46
|
+
limit?: number;
|
|
47
|
+
autoRefreshMs?: number;
|
|
48
|
+
}): {
|
|
49
|
+
metrics: OtelMetric[];
|
|
50
|
+
loading: boolean;
|
|
51
|
+
error: string | null;
|
|
52
|
+
refetch: () => Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
/** Fetch list of observed service names. */
|
|
55
|
+
export declare function useOtelServices(options: {
|
|
56
|
+
token?: string;
|
|
57
|
+
baseUrl?: string;
|
|
58
|
+
}): {
|
|
59
|
+
services: string[];
|
|
60
|
+
loading: boolean;
|
|
61
|
+
};
|
|
62
|
+
/** Fetch storage stats from the OTEL service. */
|
|
63
|
+
export declare function useOtelStats(options: {
|
|
64
|
+
token?: string;
|
|
65
|
+
baseUrl?: string;
|
|
66
|
+
}): {
|
|
67
|
+
stats: Record<string, unknown>;
|
|
68
|
+
loading: boolean;
|
|
69
|
+
};
|
|
70
|
+
export interface OtelSystemProcess {
|
|
71
|
+
memory_rss_bytes: number;
|
|
72
|
+
memory_vms_bytes: number;
|
|
73
|
+
cpu_percent: number;
|
|
74
|
+
num_threads: number;
|
|
75
|
+
error?: string;
|
|
76
|
+
}
|
|
77
|
+
export interface OtelSystemDisk {
|
|
78
|
+
data_dir: string;
|
|
79
|
+
used_bytes: number;
|
|
80
|
+
free_bytes: number;
|
|
81
|
+
total_bytes: number;
|
|
82
|
+
used_percent: number;
|
|
83
|
+
error?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface OtelSystemTable {
|
|
86
|
+
row_count: number;
|
|
87
|
+
distinct_users: number;
|
|
88
|
+
disk_bytes: number;
|
|
89
|
+
error?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface OtelSystemData {
|
|
92
|
+
process: OtelSystemProcess;
|
|
93
|
+
disk: OtelSystemDisk;
|
|
94
|
+
tables: Record<string, OtelSystemTable>;
|
|
95
|
+
total_distinct_users: number;
|
|
96
|
+
}
|
|
97
|
+
/** Fetch system statistics from /api/otel/v1/system/ (platform_admin). */
|
|
98
|
+
export declare function useOtelSystem(options: {
|
|
99
|
+
token?: string;
|
|
100
|
+
baseUrl?: string;
|
|
101
|
+
}): {
|
|
102
|
+
data: OtelSystemData | null;
|
|
103
|
+
loading: boolean;
|
|
104
|
+
error: string | null;
|
|
105
|
+
refresh: () => void;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Execute an ad-hoc DataFusion SQL query against the OTEL service.
|
|
109
|
+
*
|
|
110
|
+
* Usage:
|
|
111
|
+
* ```tsx
|
|
112
|
+
* const { execute, rows, loading, error } = useOtelQuery({ baseUrl, token });
|
|
113
|
+
* await execute('SELECT trace_id FROM spans LIMIT 10');
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare function useOtelQuery(options: {
|
|
117
|
+
token?: string;
|
|
118
|
+
baseUrl?: string;
|
|
119
|
+
}): {
|
|
120
|
+
rows: OtelQueryRow[];
|
|
121
|
+
loading: boolean;
|
|
122
|
+
error: string | null;
|
|
123
|
+
execute: (sql: string) => Promise<void>;
|
|
124
|
+
clear: () => void;
|
|
125
|
+
};
|
|
126
|
+
/** WebSocket message shape sent by the OTEL service. */
|
|
127
|
+
export interface OtelWsMessage {
|
|
128
|
+
signal: 'traces' | 'logs' | 'metrics';
|
|
129
|
+
data: Record<string, unknown>[];
|
|
130
|
+
count: number;
|
|
131
|
+
}
|
|
132
|
+
/** Callbacks for WebSocket lifecycle events. */
|
|
133
|
+
export interface OtelWsCallbacks {
|
|
134
|
+
/** Called when new traces are flushed. */
|
|
135
|
+
onTraces?: (spans: OtelSpan[]) => void;
|
|
136
|
+
/** Called when new logs are flushed. */
|
|
137
|
+
onLogs?: (logs: OtelLog[]) => void;
|
|
138
|
+
/** Called when new metrics are flushed. */
|
|
139
|
+
onMetrics?: (metrics: OtelMetric[]) => void;
|
|
140
|
+
/** Called on any message (raw). */
|
|
141
|
+
onMessage?: (msg: OtelWsMessage) => void;
|
|
142
|
+
/** Called when connection opens. */
|
|
143
|
+
onOpen?: () => void;
|
|
144
|
+
/** Called when connection closes. */
|
|
145
|
+
onClose?: (event: CloseEvent) => void;
|
|
146
|
+
/** Called on error. */
|
|
147
|
+
onError?: (event: Event) => void;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Connect to the OTEL service WebSocket for live telemetry streaming.
|
|
151
|
+
*
|
|
152
|
+
* The server pushes JSON messages whenever new data is flushed to storage.
|
|
153
|
+
* Authentication is via the `token` query parameter (JWT or API key).
|
|
154
|
+
*
|
|
155
|
+
* @returns `{ connected, error, close }` – reactive connection state.
|
|
156
|
+
*/
|
|
157
|
+
export declare function useOtelWebSocket(options: {
|
|
158
|
+
/** OTEL service base URL (e.g. `http://localhost:7800`). */
|
|
159
|
+
baseUrl?: string;
|
|
160
|
+
/** JWT token or API key for authentication. */
|
|
161
|
+
token?: string;
|
|
162
|
+
/** Whether to automatically reconnect on disconnect. Default true. */
|
|
163
|
+
autoReconnect?: boolean;
|
|
164
|
+
/** Reconnect delay in ms. Default 3000. */
|
|
165
|
+
reconnectDelayMs?: number;
|
|
166
|
+
/** Callbacks for signal events. */
|
|
167
|
+
callbacks?: OtelWsCallbacks;
|
|
168
|
+
}): {
|
|
169
|
+
connected: boolean;
|
|
170
|
+
error: string | null;
|
|
171
|
+
close: () => void;
|
|
172
|
+
};
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* React hooks for fetching OTEL signals from the REST API.
|
|
7
|
+
*
|
|
8
|
+
* @module otel/hooks
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
|
+
/**
|
|
12
|
+
* Lightweight fetch helper for OTEL API calls.
|
|
13
|
+
* Uses plain `fetch` to avoid pulling in `@jupyterlab/coreutils` / axios.
|
|
14
|
+
*/
|
|
15
|
+
async function otelFetch(url, token, options) {
|
|
16
|
+
const headers = {
|
|
17
|
+
Accept: 'application/json',
|
|
18
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
19
|
+
};
|
|
20
|
+
if (token) {
|
|
21
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
22
|
+
}
|
|
23
|
+
const isPost = options?.method === 'POST';
|
|
24
|
+
if (isPost) {
|
|
25
|
+
headers['Content-Type'] = 'application/json';
|
|
26
|
+
}
|
|
27
|
+
const res = await fetch(url, {
|
|
28
|
+
method: options?.method ?? 'GET',
|
|
29
|
+
headers,
|
|
30
|
+
credentials: 'include',
|
|
31
|
+
body: isPost && options?.body !== undefined
|
|
32
|
+
? JSON.stringify(options.body)
|
|
33
|
+
: undefined,
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
return res.json();
|
|
39
|
+
}
|
|
40
|
+
/** Convert nanosecond epoch to ISO string. Returns the input if already a string. */
|
|
41
|
+
function nanoToIso(val) {
|
|
42
|
+
if (typeof val === 'string' && val.length > 0) {
|
|
43
|
+
// Already a date string — use as-is.
|
|
44
|
+
return val;
|
|
45
|
+
}
|
|
46
|
+
const n = Number(val);
|
|
47
|
+
if (!isNaN(n) && n > 1e15) {
|
|
48
|
+
// Nanosecond epoch → milliseconds.
|
|
49
|
+
return new Date(n / 1e6).toISOString();
|
|
50
|
+
}
|
|
51
|
+
if (!isNaN(n) && n > 1e12) {
|
|
52
|
+
// Microsecond epoch.
|
|
53
|
+
return new Date(n / 1e3).toISOString();
|
|
54
|
+
}
|
|
55
|
+
if (!isNaN(n) && n > 0) {
|
|
56
|
+
// Millisecond epoch.
|
|
57
|
+
return new Date(n).toISOString();
|
|
58
|
+
}
|
|
59
|
+
return String(val ?? '');
|
|
60
|
+
}
|
|
61
|
+
/** Normalise a raw API span row into the OtelSpan shape components expect. */
|
|
62
|
+
function normalizeSpan(raw) {
|
|
63
|
+
return {
|
|
64
|
+
trace_id: String(raw.trace_id ?? raw.TraceId ?? ''),
|
|
65
|
+
span_id: String(raw.span_id ?? raw.SpanId ?? raw.trace_id ?? raw.TraceId ?? ''),
|
|
66
|
+
parent_span_id: (raw.parent_span_id ?? raw.ParentSpanId)
|
|
67
|
+
? String(raw.parent_span_id ?? raw.ParentSpanId)
|
|
68
|
+
: undefined,
|
|
69
|
+
span_name: String(raw.span_name ?? raw.SpanName ?? raw.operation_name ?? raw.name ?? ''),
|
|
70
|
+
service_name: String(raw.service_name ?? raw.ServiceName ?? ''),
|
|
71
|
+
kind: String(raw.kind ?? raw.SpanKind ?? raw.span_kind ?? 'INTERNAL'),
|
|
72
|
+
start_time: nanoToIso(raw.start_time ?? raw.Timestamp ?? raw.start_time_unix_nano),
|
|
73
|
+
end_time: nanoToIso(raw.end_time ?? raw.end_time_unix_nano),
|
|
74
|
+
duration_ms: raw.duration_ms != null
|
|
75
|
+
? Number(raw.duration_ms)
|
|
76
|
+
: raw.Duration != null
|
|
77
|
+
? Number(raw.Duration) / 1e6
|
|
78
|
+
: raw.duration_ns != null
|
|
79
|
+
? Number(raw.duration_ns) / 1e6
|
|
80
|
+
: 0,
|
|
81
|
+
status_code: raw.status_code ? String(raw.status_code) : undefined,
|
|
82
|
+
status_message: raw.status_message ? String(raw.status_message) : undefined,
|
|
83
|
+
otel_scope_name: raw.otel_scope_name
|
|
84
|
+
? String(raw.otel_scope_name)
|
|
85
|
+
: undefined,
|
|
86
|
+
attributes: typeof raw.attributes === 'object' && raw.attributes !== null
|
|
87
|
+
? raw.attributes
|
|
88
|
+
: typeof raw.attributes === 'string'
|
|
89
|
+
? (() => {
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(raw.attributes);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
})()
|
|
97
|
+
: undefined,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Normalise a raw API log row into the OtelLog shape components expect. */
|
|
101
|
+
function normalizeLog(raw) {
|
|
102
|
+
return {
|
|
103
|
+
timestamp: nanoToIso(raw.timestamp ?? raw.Timestamp ?? raw.timestamp_unix_nano),
|
|
104
|
+
severity_text: String(raw.severity_text ?? raw.SeverityText ?? ''),
|
|
105
|
+
severity_number: (raw.severity_number ?? raw.SeverityNumber) != null
|
|
106
|
+
? Number(raw.severity_number ?? raw.SeverityNumber)
|
|
107
|
+
: undefined,
|
|
108
|
+
body: String(raw.body ?? raw.Body ?? ''),
|
|
109
|
+
service_name: String(raw.service_name ?? raw.ServiceName ?? ''),
|
|
110
|
+
trace_id: (raw.trace_id ?? raw.TraceId)
|
|
111
|
+
? String(raw.trace_id ?? raw.TraceId)
|
|
112
|
+
: undefined,
|
|
113
|
+
span_id: (raw.span_id ?? raw.SpanId)
|
|
114
|
+
? String(raw.span_id ?? raw.SpanId)
|
|
115
|
+
: undefined,
|
|
116
|
+
attributes: typeof raw.attributes === 'object' && raw.attributes !== null
|
|
117
|
+
? raw.attributes
|
|
118
|
+
: typeof raw.attributes === 'string'
|
|
119
|
+
? (() => {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(raw.attributes);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
})()
|
|
127
|
+
: undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Normalise a raw API metric row into the OtelMetric shape components expect. */
|
|
131
|
+
function normalizeMetric(raw) {
|
|
132
|
+
return {
|
|
133
|
+
metric_name: String(raw.metric_name ?? raw.MetricName ?? raw.name ?? raw.Name ?? ''),
|
|
134
|
+
service_name: String(raw.service_name ?? raw.ServiceName ?? ''),
|
|
135
|
+
value: Number(raw.value ?? raw.Value ?? raw.value_double ?? raw.value_int ?? 0),
|
|
136
|
+
unit: (raw.metric_unit ?? raw.MetricUnit)
|
|
137
|
+
? String(raw.metric_unit ?? raw.MetricUnit)
|
|
138
|
+
: (raw.unit ?? raw.Unit)
|
|
139
|
+
? String(raw.unit ?? raw.Unit)
|
|
140
|
+
: undefined,
|
|
141
|
+
timestamp: nanoToIso(raw.timestamp ??
|
|
142
|
+
raw.Timestamp ??
|
|
143
|
+
raw.timestamp_unix_nano ??
|
|
144
|
+
raw.start_time_unix_nano),
|
|
145
|
+
metric_type: (raw.metric_type ?? raw.MetricType)
|
|
146
|
+
? String(raw.metric_type ?? raw.MetricType)
|
|
147
|
+
: undefined,
|
|
148
|
+
attributes: typeof raw.attributes === 'object' && raw.attributes !== null
|
|
149
|
+
? raw.attributes
|
|
150
|
+
: typeof raw.attributes === 'string'
|
|
151
|
+
? (() => {
|
|
152
|
+
try {
|
|
153
|
+
return JSON.parse(raw.attributes);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
})()
|
|
159
|
+
: undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// ── useOtelTraces ───────────────────────────────────────────────────
|
|
163
|
+
/** Fetch a list of traces / spans from the OTEL service. */
|
|
164
|
+
export function useOtelTraces(options) {
|
|
165
|
+
const { token, baseUrl = '', serviceName, limit = 50, autoRefreshMs, } = options;
|
|
166
|
+
const [traces, setTraces] = useState([]);
|
|
167
|
+
const [loading, setLoading] = useState(true);
|
|
168
|
+
const [error, setError] = useState(null);
|
|
169
|
+
const fetchTraces = useCallback(async () => {
|
|
170
|
+
try {
|
|
171
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
172
|
+
if (serviceName)
|
|
173
|
+
params.set('service_name', serviceName);
|
|
174
|
+
const data = await otelFetch(`${baseUrl}/api/otel/v1/traces/?${params}`, token);
|
|
175
|
+
const rows = data.data ?? data ?? [];
|
|
176
|
+
setTraces(Array.isArray(rows) ? rows.map(normalizeSpan) : []);
|
|
177
|
+
setError(null);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
setError(err.message);
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
setLoading(false);
|
|
184
|
+
}
|
|
185
|
+
}, [token, baseUrl, serviceName, limit]);
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
fetchTraces();
|
|
188
|
+
if (autoRefreshMs && autoRefreshMs > 0) {
|
|
189
|
+
const id = setInterval(fetchTraces, autoRefreshMs);
|
|
190
|
+
return () => clearInterval(id);
|
|
191
|
+
}
|
|
192
|
+
}, [fetchTraces, autoRefreshMs]);
|
|
193
|
+
return { traces, loading, error, refetch: fetchTraces };
|
|
194
|
+
}
|
|
195
|
+
// ── useOtelTrace ────────────────────────────────────────────────────
|
|
196
|
+
/** Fetch all spans for a single trace. */
|
|
197
|
+
export function useOtelTrace(options) {
|
|
198
|
+
const { traceId, token, baseUrl = '' } = options;
|
|
199
|
+
const [spans, setSpans] = useState([]);
|
|
200
|
+
const [loading, setLoading] = useState(false);
|
|
201
|
+
const [error, setError] = useState(null);
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (!traceId) {
|
|
204
|
+
setSpans([]);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
setLoading(true);
|
|
208
|
+
otelFetch(`${baseUrl}/api/otel/v1/traces/${traceId}`, token)
|
|
209
|
+
.then(data => {
|
|
210
|
+
const rows = data.data ?? data ?? [];
|
|
211
|
+
setSpans(Array.isArray(rows) ? rows.map(normalizeSpan) : []);
|
|
212
|
+
setError(null);
|
|
213
|
+
})
|
|
214
|
+
.catch((err) => setError(err.message))
|
|
215
|
+
.finally(() => setLoading(false));
|
|
216
|
+
}, [traceId, token, baseUrl]);
|
|
217
|
+
return { spans, loading, error };
|
|
218
|
+
}
|
|
219
|
+
// ── useOtelLogs ─────────────────────────────────────────────────────
|
|
220
|
+
/** Fetch log records from the OTEL service. */
|
|
221
|
+
export function useOtelLogs(options) {
|
|
222
|
+
const { token, baseUrl = '', serviceName, severity, traceId, limit = 100, autoRefreshMs, } = options;
|
|
223
|
+
const [logs, setLogs] = useState([]);
|
|
224
|
+
const [loading, setLoading] = useState(true);
|
|
225
|
+
const [error, setError] = useState(null);
|
|
226
|
+
const fetchLogs = useCallback(async () => {
|
|
227
|
+
try {
|
|
228
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
229
|
+
if (serviceName)
|
|
230
|
+
params.set('service_name', serviceName);
|
|
231
|
+
if (severity)
|
|
232
|
+
params.set('severity', severity);
|
|
233
|
+
if (traceId)
|
|
234
|
+
params.set('trace_id', traceId);
|
|
235
|
+
const data = await otelFetch(`${baseUrl}/api/otel/v1/logs/?${params}`, token);
|
|
236
|
+
const rows = data.data ?? data ?? [];
|
|
237
|
+
setLogs(Array.isArray(rows) ? rows.map(normalizeLog) : []);
|
|
238
|
+
setError(null);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
setError(err.message);
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
setLoading(false);
|
|
245
|
+
}
|
|
246
|
+
}, [token, baseUrl, serviceName, severity, traceId, limit]);
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
fetchLogs();
|
|
249
|
+
if (autoRefreshMs && autoRefreshMs > 0) {
|
|
250
|
+
const id = setInterval(fetchLogs, autoRefreshMs);
|
|
251
|
+
return () => clearInterval(id);
|
|
252
|
+
}
|
|
253
|
+
}, [fetchLogs, autoRefreshMs]);
|
|
254
|
+
return { logs, loading, error, refetch: fetchLogs };
|
|
255
|
+
}
|
|
256
|
+
// ── useOtelMetrics ──────────────────────────────────────────────────
|
|
257
|
+
/** Fetch metrics from the OTEL service. */
|
|
258
|
+
export function useOtelMetrics(options) {
|
|
259
|
+
const { token, baseUrl = '', serviceName, metricName, limit = 50, autoRefreshMs, } = options;
|
|
260
|
+
const [metrics, setMetrics] = useState([]);
|
|
261
|
+
const [loading, setLoading] = useState(true);
|
|
262
|
+
const [error, setError] = useState(null);
|
|
263
|
+
const fetchMetrics = useCallback(async () => {
|
|
264
|
+
try {
|
|
265
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
266
|
+
if (serviceName)
|
|
267
|
+
params.set('service_name', serviceName);
|
|
268
|
+
if (metricName)
|
|
269
|
+
params.set('metric_name', metricName);
|
|
270
|
+
const data = await otelFetch(`${baseUrl}/api/otel/v1/metrics/?${params}`, token);
|
|
271
|
+
const rows = data.data ?? data ?? [];
|
|
272
|
+
setMetrics(Array.isArray(rows) ? rows.map(normalizeMetric) : []);
|
|
273
|
+
setError(null);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
setError(err.message);
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
setLoading(false);
|
|
280
|
+
}
|
|
281
|
+
}, [token, baseUrl, serviceName, metricName, limit]);
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
fetchMetrics();
|
|
284
|
+
if (autoRefreshMs && autoRefreshMs > 0) {
|
|
285
|
+
const id = setInterval(fetchMetrics, autoRefreshMs);
|
|
286
|
+
return () => clearInterval(id);
|
|
287
|
+
}
|
|
288
|
+
}, [fetchMetrics, autoRefreshMs]);
|
|
289
|
+
return { metrics, loading, error, refetch: fetchMetrics };
|
|
290
|
+
}
|
|
291
|
+
// ── useOtelServices ─────────────────────────────────────────────────
|
|
292
|
+
/** Fetch list of observed service names. */
|
|
293
|
+
export function useOtelServices(options) {
|
|
294
|
+
const { token, baseUrl = '' } = options;
|
|
295
|
+
const [services, setServices] = useState([]);
|
|
296
|
+
const [loading, setLoading] = useState(true);
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
otelFetch(`${baseUrl}/api/otel/v1/traces/services/list`, token)
|
|
299
|
+
.then(data => {
|
|
300
|
+
// Handle various response shapes:
|
|
301
|
+
// { services: ["a","b"] } | { data: [{service_name:"a"},...] } | ["a","b"]
|
|
302
|
+
let raw = data.services ?? data.data ?? data;
|
|
303
|
+
if (Array.isArray(raw) &&
|
|
304
|
+
raw.length > 0 &&
|
|
305
|
+
typeof raw[0] === 'object' &&
|
|
306
|
+
raw[0] !== null) {
|
|
307
|
+
raw = raw.map(r => (r.service_name ?? r.name ?? ''));
|
|
308
|
+
}
|
|
309
|
+
setServices(Array.isArray(raw) ? raw : []);
|
|
310
|
+
})
|
|
311
|
+
.catch(() => { })
|
|
312
|
+
.finally(() => setLoading(false));
|
|
313
|
+
}, [token, baseUrl]);
|
|
314
|
+
return { services, loading };
|
|
315
|
+
}
|
|
316
|
+
// ── useOtelStats ────────────────────────────────────────────────────
|
|
317
|
+
/** Fetch storage stats from the OTEL service. */
|
|
318
|
+
export function useOtelStats(options) {
|
|
319
|
+
const { token, baseUrl = '' } = options;
|
|
320
|
+
const [stats, setStats] = useState({});
|
|
321
|
+
const [loading, setLoading] = useState(true);
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
otelFetch(`${baseUrl}/api/otel/v1/stats/`, token)
|
|
324
|
+
.then(data => setStats(data))
|
|
325
|
+
.catch(() => { })
|
|
326
|
+
.finally(() => setLoading(false));
|
|
327
|
+
}, [token, baseUrl]);
|
|
328
|
+
return { stats, loading };
|
|
329
|
+
}
|
|
330
|
+
/** Fetch system statistics from /api/otel/v1/system/ (platform_admin). */
|
|
331
|
+
export function useOtelSystem(options) {
|
|
332
|
+
const { token, baseUrl = '' } = options;
|
|
333
|
+
const [data, setData] = useState(null);
|
|
334
|
+
const [loading, setLoading] = useState(true);
|
|
335
|
+
const [error, setError] = useState(null);
|
|
336
|
+
const refresh = useCallback(() => {
|
|
337
|
+
setLoading(true);
|
|
338
|
+
setError(null);
|
|
339
|
+
otelFetch(`${baseUrl}/api/otel/v1/system/`, token)
|
|
340
|
+
.then((resp) => setData(resp.data ?? resp))
|
|
341
|
+
.catch((err) => setError(err?.message ?? 'Failed to load system info'))
|
|
342
|
+
.finally(() => setLoading(false));
|
|
343
|
+
}, [token, baseUrl]);
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
refresh();
|
|
346
|
+
}, [refresh]);
|
|
347
|
+
return { data, loading, error, refresh };
|
|
348
|
+
}
|
|
349
|
+
// ── useOtelQuery ────────────────────────────────────────────────────
|
|
350
|
+
/**
|
|
351
|
+
* Execute an ad-hoc DataFusion SQL query against the OTEL service.
|
|
352
|
+
*
|
|
353
|
+
* Usage:
|
|
354
|
+
* ```tsx
|
|
355
|
+
* const { execute, rows, loading, error } = useOtelQuery({ baseUrl, token });
|
|
356
|
+
* await execute('SELECT trace_id FROM spans LIMIT 10');
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
export function useOtelQuery(options) {
|
|
360
|
+
const { token, baseUrl = '' } = options;
|
|
361
|
+
const [rows, setRows] = useState([]);
|
|
362
|
+
const [loading, setLoading] = useState(false);
|
|
363
|
+
const [error, setError] = useState(null);
|
|
364
|
+
const execute = useCallback(async (sql) => {
|
|
365
|
+
setLoading(true);
|
|
366
|
+
setError(null);
|
|
367
|
+
try {
|
|
368
|
+
const data = await otelFetch(`${baseUrl}/api/otel/v1/query/`, token, {
|
|
369
|
+
method: 'POST',
|
|
370
|
+
body: { sql },
|
|
371
|
+
});
|
|
372
|
+
setRows(data.data ?? []);
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
setError(err.message ?? 'Query failed');
|
|
376
|
+
setRows([]);
|
|
377
|
+
}
|
|
378
|
+
finally {
|
|
379
|
+
setLoading(false);
|
|
380
|
+
}
|
|
381
|
+
}, [token, baseUrl]);
|
|
382
|
+
const clear = useCallback(() => {
|
|
383
|
+
setRows([]);
|
|
384
|
+
setError(null);
|
|
385
|
+
}, []);
|
|
386
|
+
return { rows, loading, error, execute, clear };
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Connect to the OTEL service WebSocket for live telemetry streaming.
|
|
390
|
+
*
|
|
391
|
+
* The server pushes JSON messages whenever new data is flushed to storage.
|
|
392
|
+
* Authentication is via the `token` query parameter (JWT or API key).
|
|
393
|
+
*
|
|
394
|
+
* @returns `{ connected, error, close }` – reactive connection state.
|
|
395
|
+
*/
|
|
396
|
+
export function useOtelWebSocket(options) {
|
|
397
|
+
const { baseUrl = '', token, autoReconnect = true, reconnectDelayMs = 3000, callbacks, } = options;
|
|
398
|
+
const [connected, setConnected] = useState(false);
|
|
399
|
+
const [error, setError] = useState(null);
|
|
400
|
+
const wsRef = useRef(null);
|
|
401
|
+
const reconnectTimerRef = useRef(null);
|
|
402
|
+
// Store callbacks in a ref so we don't reconnect when they change.
|
|
403
|
+
const cbRef = useRef(callbacks);
|
|
404
|
+
cbRef.current = callbacks;
|
|
405
|
+
const close = useCallback(() => {
|
|
406
|
+
if (reconnectTimerRef.current) {
|
|
407
|
+
clearTimeout(reconnectTimerRef.current);
|
|
408
|
+
reconnectTimerRef.current = null;
|
|
409
|
+
}
|
|
410
|
+
if (wsRef.current) {
|
|
411
|
+
wsRef.current.close();
|
|
412
|
+
wsRef.current = null;
|
|
413
|
+
}
|
|
414
|
+
}, []);
|
|
415
|
+
useEffect(() => {
|
|
416
|
+
if (!token) {
|
|
417
|
+
setConnected(false);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
// Derive ws(s):// URL from http(s):// baseUrl.
|
|
421
|
+
let wsUrl;
|
|
422
|
+
if (baseUrl.startsWith('http://')) {
|
|
423
|
+
wsUrl = `ws://${baseUrl.slice(7)}`;
|
|
424
|
+
}
|
|
425
|
+
else if (baseUrl.startsWith('https://')) {
|
|
426
|
+
wsUrl = `wss://${baseUrl.slice(8)}`;
|
|
427
|
+
}
|
|
428
|
+
else if (baseUrl.startsWith('ws://') || baseUrl.startsWith('wss://')) {
|
|
429
|
+
wsUrl = baseUrl;
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// Relative URL – use current page's protocol.
|
|
433
|
+
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
434
|
+
wsUrl = `${proto}//${window.location.host}${baseUrl}`;
|
|
435
|
+
}
|
|
436
|
+
wsUrl = `${wsUrl.replace(/\/$/, '')}/api/otel/v1/ws?token=${encodeURIComponent(token)}`;
|
|
437
|
+
function connect() {
|
|
438
|
+
const ws = new WebSocket(wsUrl);
|
|
439
|
+
wsRef.current = ws;
|
|
440
|
+
ws.onopen = () => {
|
|
441
|
+
setConnected(true);
|
|
442
|
+
setError(null);
|
|
443
|
+
cbRef.current?.onOpen?.();
|
|
444
|
+
};
|
|
445
|
+
ws.onmessage = event => {
|
|
446
|
+
try {
|
|
447
|
+
const msg = JSON.parse(event.data);
|
|
448
|
+
cbRef.current?.onMessage?.(msg);
|
|
449
|
+
if (msg.signal === 'traces' && cbRef.current?.onTraces) {
|
|
450
|
+
cbRef.current.onTraces(Array.isArray(msg.data) ? msg.data.map(normalizeSpan) : []);
|
|
451
|
+
}
|
|
452
|
+
if (msg.signal === 'logs' && cbRef.current?.onLogs) {
|
|
453
|
+
cbRef.current.onLogs(Array.isArray(msg.data) ? msg.data.map(normalizeLog) : []);
|
|
454
|
+
}
|
|
455
|
+
if (msg.signal === 'metrics' && cbRef.current?.onMetrics) {
|
|
456
|
+
cbRef.current.onMetrics(Array.isArray(msg.data) ? msg.data.map(normalizeMetric) : []);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
// Ignore malformed messages.
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
ws.onclose = event => {
|
|
464
|
+
setConnected(false);
|
|
465
|
+
cbRef.current?.onClose?.(event);
|
|
466
|
+
if (autoReconnect && event.code !== 1008) {
|
|
467
|
+
// 1008 = policy violation (auth failure) – don't retry.
|
|
468
|
+
reconnectTimerRef.current = setTimeout(connect, reconnectDelayMs);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
ws.onerror = event => {
|
|
472
|
+
setError('WebSocket error');
|
|
473
|
+
cbRef.current?.onError?.(event);
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
connect();
|
|
477
|
+
return () => {
|
|
478
|
+
// Disable auto-reconnect during teardown.
|
|
479
|
+
if (reconnectTimerRef.current) {
|
|
480
|
+
clearTimeout(reconnectTimerRef.current);
|
|
481
|
+
reconnectTimerRef.current = null;
|
|
482
|
+
}
|
|
483
|
+
if (wsRef.current) {
|
|
484
|
+
wsRef.current.close();
|
|
485
|
+
wsRef.current = null;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}, [baseUrl, token, autoReconnect, reconnectDelayMs]);
|
|
489
|
+
return { connected, error, close };
|
|
490
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTEL React components for visualizing OpenTelemetry signals
|
|
3
|
+
* (traces, logs, metrics) in a Logfire-inspired Live view.
|
|
4
|
+
*
|
|
5
|
+
* @module otel
|
|
6
|
+
*/
|
|
7
|
+
export type { OtelSpan, OtelLog, OtelMetric, OtelSpanEvent, OtelSpanLink, SignalType, OtelLiveProps, OtelTracesListProps, OtelSpanDetailProps, OtelTimelineProps, OtelLogsListProps, OtelMetricsListProps, OtelSpanTreeProps, OtelSearchBarProps, OtelTimelineRangeSliderProps, OtelQueryRow, OtelQueryResult, } from './types';
|
|
8
|
+
export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, } from './hooks';
|
|
9
|
+
export type { OtelWsMessage, OtelWsCallbacks } from './hooks';
|
|
10
|
+
export { OtelTimeline } from './OtelTimeline';
|
|
11
|
+
export { OtelTracesList } from './OtelTracesList';
|
|
12
|
+
export { OtelSpanDetail } from './OtelSpanDetail';
|
|
13
|
+
export { OtelSpanTree } from './OtelSpanTree';
|
|
14
|
+
export { OtelLogsList } from './OtelLogsList';
|
|
15
|
+
export { OtelSearchBar } from './OtelSearchBar';
|
|
16
|
+
export { OtelMetricsList } from './OtelMetricsList';
|
|
17
|
+
export { OtelMetricsChart } from './OtelMetricsChart';
|
|
18
|
+
export type { OtelMetricsChartProps } from './OtelMetricsChart';
|
|
19
|
+
export { OtelLive } from './OtelLive';
|
|
20
|
+
export { OtelTimelineRangeSlider } from './OtelTimelineRangeSlider';
|
|
21
|
+
export { OtelSqlView } from './OtelSqlView';
|
|
22
|
+
export type { OtelSqlViewProps } from './OtelSqlView';
|
|
23
|
+
export { OtelSystemView } from './OtelSystemView';
|
|
24
|
+
export type { OtelSystemViewProps } from './OtelSystemView';
|
|
25
|
+
export type { OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './hooks';
|