@datalayer/core 1.0.14 → 1.0.16
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/DatalayerApi.js +5 -0
- package/lib/api/constants.d.ts +1 -1
- package/lib/api/constants.js +1 -1
- package/lib/api/spacer/lexicals.d.ts +8 -0
- package/lib/api/spacer/lexicals.js +14 -0
- package/lib/api/spacer/notebooks.d.ts +8 -0
- package/lib/api/spacer/notebooks.js +14 -0
- package/lib/api/spacer/spaces.d.ts +76 -1
- package/lib/api/spacer/spaces.js +140 -0
- package/lib/client/auth/storage.d.ts +3 -1
- package/lib/client/auth/storage.js +34 -13
- package/lib/client/index.d.ts +22 -2
- package/lib/client/index.js +2 -1
- package/lib/client/mixins/SpacerMixin.d.ts +99 -0
- package/lib/client/mixins/SpacerMixin.js +254 -0
- package/lib/client/utils/slugify.d.ts +8 -0
- package/lib/client/utils/slugify.js +18 -0
- package/lib/components/sparklines/dataProcessing.d.ts +2 -4
- package/lib/components/sparklines/dataProcessing.js +0 -4
- package/lib/components/sparklines/index.d.ts +1 -0
- package/lib/config/Configuration.d.ts +1 -1
- package/lib/config/Configuration.js +1 -1
- package/lib/hooks/useCache.js +1 -1
- package/lib/hooks/useProjects.d.ts +2 -8
- package/lib/hooks/useProjects.js +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/models/EnvironmentDTO.d.ts +1 -1
- package/lib/models/EnvironmentDTO.js +1 -1
- package/lib/models/ProjectDTO.d.ts +89 -0
- package/lib/models/ProjectDTO.js +131 -0
- package/lib/models/Space.d.ts +1 -1
- package/lib/models/SpaceDTO.d.ts +61 -0
- package/lib/otel/auth.d.ts +11 -0
- package/lib/otel/auth.js +55 -0
- package/lib/otel/client/OtelClient.d.ts +4 -1
- package/lib/otel/client/OtelClient.js +30 -13
- package/lib/otel/hooks/index.d.ts +2 -0
- package/lib/otel/hooks/index.js +18 -21
- package/lib/otel/index.d.ts +2 -0
- package/lib/otel/index.js +2 -0
- package/lib/otel/types.d.ts +3 -1
- package/lib/otel/views/OtelMetricsList.d.ts +2 -5
- package/lib/otel/views/index.d.ts +1 -0
- package/lib/services/DatalayerServiceManager.d.ts +1 -1
- package/lib/services/DatalayerServiceManager.js +1 -1
- package/lib/state/storage/IAMStorage.js +29 -4
- package/lib/state/substates/CoreState.js +1 -1
- package/lib/views/otel/simpleAuthStore.js +1 -1
- package/package.json +1 -1
package/lib/models/Space.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { IUser } from './User';
|
|
|
10
10
|
* @returns Space
|
|
11
11
|
*/
|
|
12
12
|
export declare const asSpace: (raw_space: any) => ISpace;
|
|
13
|
-
export type ISpaceVariant = 'default' | 'course';
|
|
13
|
+
export type ISpaceVariant = 'default' | 'course' | 'project';
|
|
14
14
|
export type IAnySpace = ISpace | ICourse;
|
|
15
15
|
export type IBaseSpace = {
|
|
16
16
|
id: string;
|
package/lib/models/SpaceDTO.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { LexicalDTO, type LexicalJSON } from './LexicalDTO';
|
|
|
8
8
|
*/
|
|
9
9
|
export interface SpaceData {
|
|
10
10
|
uid: string;
|
|
11
|
+
id?: string;
|
|
11
12
|
name_t: string;
|
|
12
13
|
handle_s: string;
|
|
13
14
|
variant_s: string;
|
|
@@ -15,6 +16,11 @@ export interface SpaceData {
|
|
|
15
16
|
tags_ss?: string[];
|
|
16
17
|
members?: any[];
|
|
17
18
|
items?: any[];
|
|
19
|
+
public_b?: boolean;
|
|
20
|
+
creation_ts_dt?: string;
|
|
21
|
+
attached_agent_pod_name_s?: string;
|
|
22
|
+
attached_agent_spec_id_s?: string;
|
|
23
|
+
attached_agent_given_name_s?: string;
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
20
26
|
* Stable public interface for Space data.
|
|
@@ -267,3 +273,58 @@ export interface SpacesForUserResponse {
|
|
|
267
273
|
message: string;
|
|
268
274
|
spaces: SpaceData[];
|
|
269
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Request payload for updating a space.
|
|
278
|
+
* Supports arbitrary Solr fields via index signature.
|
|
279
|
+
* @interface UpdateSpaceRequest
|
|
280
|
+
*/
|
|
281
|
+
export interface UpdateSpaceRequest {
|
|
282
|
+
name?: string;
|
|
283
|
+
description?: string;
|
|
284
|
+
[key: string]: unknown;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Response from getting a space by ID
|
|
288
|
+
* @interface GetSpaceResponse
|
|
289
|
+
*/
|
|
290
|
+
export interface GetSpaceResponse {
|
|
291
|
+
success: boolean;
|
|
292
|
+
message: string;
|
|
293
|
+
space?: SpaceData;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Response from updating a space
|
|
297
|
+
* @interface UpdateSpaceResponse
|
|
298
|
+
*/
|
|
299
|
+
export interface UpdateSpaceResponse {
|
|
300
|
+
success: boolean;
|
|
301
|
+
message: string;
|
|
302
|
+
space?: SpaceData;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Response from deleting a space
|
|
306
|
+
* @interface DeleteSpaceResponse
|
|
307
|
+
*/
|
|
308
|
+
export interface DeleteSpaceResponse {
|
|
309
|
+
success: boolean;
|
|
310
|
+
message: string;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Response from getting default items for a space
|
|
314
|
+
* @interface GetSpaceDefaultItemsResponse
|
|
315
|
+
*/
|
|
316
|
+
export interface GetSpaceDefaultItemsResponse {
|
|
317
|
+
success: boolean;
|
|
318
|
+
message: string;
|
|
319
|
+
default_notebook_uid: string | null;
|
|
320
|
+
default_document_uid: string | null;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Response from getting spaces by type
|
|
324
|
+
* @interface GetSpacesByTypeResponse
|
|
325
|
+
*/
|
|
326
|
+
export interface GetSpacesByTypeResponse {
|
|
327
|
+
success: boolean;
|
|
328
|
+
message: string;
|
|
329
|
+
spaces: SpaceData[];
|
|
330
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ResolvedOtelAuth {
|
|
2
|
+
token: string;
|
|
3
|
+
userUid: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function resolveOtelUserUid(token?: string, userUid?: string): string | null;
|
|
6
|
+
export declare function resolveOtelAuth(token?: string, userUid?: string): ResolvedOtelAuth;
|
|
7
|
+
export declare function buildOtelWebSocketUrl(options: {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
token: string;
|
|
10
|
+
userUid?: string;
|
|
11
|
+
}): string;
|
package/lib/otel/auth.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { getDatalayerJwtUser, parseJwtPayload } from '../utils/Jwt.js';
|
|
6
|
+
function normalizeBaseUrl(baseUrl) {
|
|
7
|
+
const normalized = baseUrl.startsWith('ws://') || baseUrl.startsWith('wss://')
|
|
8
|
+
? baseUrl.replace(/^ws/, 'http')
|
|
9
|
+
: baseUrl;
|
|
10
|
+
return new URL(normalized);
|
|
11
|
+
}
|
|
12
|
+
export function resolveOtelUserUid(token, userUid) {
|
|
13
|
+
if (typeof userUid === 'string' && userUid.trim().length > 0) {
|
|
14
|
+
return userUid.trim();
|
|
15
|
+
}
|
|
16
|
+
if (!token) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const user = getDatalayerJwtUser(token);
|
|
20
|
+
if (user?.uid) {
|
|
21
|
+
return user.uid;
|
|
22
|
+
}
|
|
23
|
+
const payload = parseJwtPayload(token);
|
|
24
|
+
const sub = payload?.sub;
|
|
25
|
+
if (typeof sub === 'string' && sub.trim().length > 0) {
|
|
26
|
+
return sub.trim();
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export function resolveOtelAuth(token, userUid) {
|
|
31
|
+
if (!token || token.trim().length === 0) {
|
|
32
|
+
throw new Error('OTEL requires an authenticated token.');
|
|
33
|
+
}
|
|
34
|
+
const resolvedUserUid = resolveOtelUserUid(token, userUid);
|
|
35
|
+
if (!resolvedUserUid) {
|
|
36
|
+
throw new Error('OTEL requires datalayer.user_uid. Provide a JWT with user.uid/sub or pass userUid explicitly.');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
token,
|
|
40
|
+
userUid: resolvedUserUid,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function buildOtelWebSocketUrl(options) {
|
|
44
|
+
const auth = resolveOtelAuth(options.token, options.userUid);
|
|
45
|
+
const url = normalizeBaseUrl(options.baseUrl);
|
|
46
|
+
const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
47
|
+
const pathname = url.pathname.replace(/\/$/, '');
|
|
48
|
+
const hasApiPrefix = pathname.endsWith('/api/otel/v1') || pathname.includes('/api/otel/v1/');
|
|
49
|
+
url.protocol = wsProtocol;
|
|
50
|
+
url.pathname = hasApiPrefix ? `${pathname}/ws` : '/api/otel/v1/ws';
|
|
51
|
+
url.search = '';
|
|
52
|
+
url.searchParams.set('token', auth.token);
|
|
53
|
+
url.searchParams.set('user_uid', auth.userUid);
|
|
54
|
+
return url.toString();
|
|
55
|
+
}
|
|
@@ -9,6 +9,8 @@ export interface OtelClientOptions {
|
|
|
9
9
|
baseUrl?: string;
|
|
10
10
|
/** JWT bearer token or API key for authentication. */
|
|
11
11
|
token?: string;
|
|
12
|
+
/** Explicit user UID; optional when token contains user.uid/sub. */
|
|
13
|
+
userUid?: string;
|
|
12
14
|
}
|
|
13
15
|
export interface FetchTracesOptions {
|
|
14
16
|
serviceName?: string;
|
|
@@ -37,7 +39,8 @@ export interface FetchMetricTotalOptions {
|
|
|
37
39
|
*/
|
|
38
40
|
export declare class OtelClient {
|
|
39
41
|
private readonly baseUrl;
|
|
40
|
-
private readonly token
|
|
42
|
+
private readonly token;
|
|
43
|
+
private readonly userUid;
|
|
41
44
|
constructor(options?: OtelClientOptions);
|
|
42
45
|
/** List recent spans / traces. */
|
|
43
46
|
fetchTraces(options?: FetchTracesOptions): Promise<{
|
|
@@ -17,15 +17,16 @@
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
import { coreStore } from '../../state/substates/CoreState.js';
|
|
20
|
+
import { resolveOtelAuth } from '../auth.js';
|
|
20
21
|
// ── Fetch helper ────────────────────────────────────────────────────────────
|
|
21
|
-
async function otelFetch(url, token, options) {
|
|
22
|
+
async function otelFetch(url, token, userUid, options) {
|
|
23
|
+
const auth = resolveOtelAuth(token, userUid);
|
|
22
24
|
const headers = {
|
|
23
25
|
Accept: 'application/json',
|
|
24
26
|
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
25
27
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
28
|
+
headers['Authorization'] = `Bearer ${auth.token}`;
|
|
29
|
+
headers['X-Datalayer-User-Uid'] = auth.userUid;
|
|
29
30
|
if (options?.body) {
|
|
30
31
|
headers['Content-Type'] = 'application/json';
|
|
31
32
|
}
|
|
@@ -76,6 +77,18 @@ function parseMetricTimestamp(raw) {
|
|
|
76
77
|
return new Date(numeric).toISOString();
|
|
77
78
|
}
|
|
78
79
|
function normalizeMetric(raw) {
|
|
80
|
+
// Preserve the raw nanosecond timestamp for time-series charts.
|
|
81
|
+
let timestampUnixNano;
|
|
82
|
+
const rawNano = raw.timestamp_unix_nano;
|
|
83
|
+
if (typeof rawNano === 'number' && rawNano > 0) {
|
|
84
|
+
timestampUnixNano = rawNano;
|
|
85
|
+
}
|
|
86
|
+
else if (typeof rawNano === 'string') {
|
|
87
|
+
const parsed = Number(rawNano);
|
|
88
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
89
|
+
timestampUnixNano = parsed;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
79
92
|
return {
|
|
80
93
|
metric_name: String(raw.metric_name ?? raw.MetricName ?? raw.name ?? raw.Name ?? ''),
|
|
81
94
|
service_name: String(raw.service_name ?? raw.ServiceName ?? ''),
|
|
@@ -90,6 +103,7 @@ function normalizeMetric(raw) {
|
|
|
90
103
|
? String(raw.Unit)
|
|
91
104
|
: undefined,
|
|
92
105
|
timestamp: parseMetricTimestamp(raw),
|
|
106
|
+
timestamp_unix_nano: timestampUnixNano,
|
|
93
107
|
metric_type: raw.metric_type != null
|
|
94
108
|
? String(raw.metric_type)
|
|
95
109
|
: raw.MetricType != null
|
|
@@ -109,10 +123,13 @@ function normalizeMetric(raw) {
|
|
|
109
123
|
export class OtelClient {
|
|
110
124
|
baseUrl;
|
|
111
125
|
token;
|
|
126
|
+
userUid;
|
|
112
127
|
constructor(options = {}) {
|
|
113
128
|
this.baseUrl =
|
|
114
129
|
options.baseUrl ?? coreStore.getState().configuration.otelRunUrl;
|
|
115
|
-
|
|
130
|
+
const auth = resolveOtelAuth(options.token, options.userUid);
|
|
131
|
+
this.token = auth.token;
|
|
132
|
+
this.userUid = auth.userUid;
|
|
116
133
|
}
|
|
117
134
|
// ── Traces ────────────────────────────────────────────────────────────────
|
|
118
135
|
/** List recent spans / traces. */
|
|
@@ -120,13 +137,13 @@ export class OtelClient {
|
|
|
120
137
|
const params = new URLSearchParams({ limit: String(options.limit ?? 50) });
|
|
121
138
|
if (options.serviceName)
|
|
122
139
|
params.set('service_name', options.serviceName);
|
|
123
|
-
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/?${params}`, this.token);
|
|
140
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/?${params}`, this.token, this.userUid);
|
|
124
141
|
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
125
142
|
return { data: rows, count: rows.length };
|
|
126
143
|
}
|
|
127
144
|
/** Fetch all spans for a single trace by `traceId`. */
|
|
128
145
|
async fetchTrace(traceId) {
|
|
129
|
-
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/${traceId}`, this.token);
|
|
146
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/${traceId}`, this.token, this.userUid);
|
|
130
147
|
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
131
148
|
return { data: rows };
|
|
132
149
|
}
|
|
@@ -140,7 +157,7 @@ export class OtelClient {
|
|
|
140
157
|
params.set('severity', options.severity);
|
|
141
158
|
if (options.traceId)
|
|
142
159
|
params.set('trace_id', options.traceId);
|
|
143
|
-
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/logs/?${params}`, this.token);
|
|
160
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/logs/?${params}`, this.token, this.userUid);
|
|
144
161
|
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
145
162
|
return { data: rows, count: rows.length };
|
|
146
163
|
}
|
|
@@ -152,7 +169,7 @@ export class OtelClient {
|
|
|
152
169
|
params.set('service_name', options.serviceName);
|
|
153
170
|
if (options.metricName)
|
|
154
171
|
params.set('metric_name', options.metricName);
|
|
155
|
-
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/metrics/?${params}`, this.token);
|
|
172
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/metrics/?${params}`, this.token, this.userUid);
|
|
156
173
|
const rows = (Array.isArray(resp) ? resp : resp.data) ?? [];
|
|
157
174
|
const normalized = Array.isArray(rows)
|
|
158
175
|
? rows.map(row => normalizeMetric(row))
|
|
@@ -177,7 +194,7 @@ export class OtelClient {
|
|
|
177
194
|
// ── Services ──────────────────────────────────────────────────────────────
|
|
178
195
|
/** List known service names observed in traces. */
|
|
179
196
|
async fetchServices() {
|
|
180
|
-
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/services/list`, this.token);
|
|
197
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/traces/services/list`, this.token, this.userUid);
|
|
181
198
|
if (Array.isArray(resp))
|
|
182
199
|
return resp;
|
|
183
200
|
const byServices = resp.services;
|
|
@@ -192,7 +209,7 @@ export class OtelClient {
|
|
|
192
209
|
// ── Stats ─────────────────────────────────────────────────────────────────
|
|
193
210
|
/** Fetch storage / ingestion statistics. */
|
|
194
211
|
async fetchStats() {
|
|
195
|
-
return otelFetch(`${this.baseUrl}/api/otel/v1/stats/`, this.token);
|
|
212
|
+
return otelFetch(`${this.baseUrl}/api/otel/v1/stats/`, this.token, this.userUid);
|
|
196
213
|
}
|
|
197
214
|
// ── SQL query ─────────────────────────────────────────────────────────────
|
|
198
215
|
/**
|
|
@@ -201,7 +218,7 @@ export class OtelClient {
|
|
|
201
218
|
* Available tables: `spans`, `logs`, `metrics`.
|
|
202
219
|
*/
|
|
203
220
|
async executeQuery(sql) {
|
|
204
|
-
return otelFetch(`${this.baseUrl}/api/otel/v1/query/`, this.token, { method: 'POST', body: { sql } });
|
|
221
|
+
return otelFetch(`${this.baseUrl}/api/otel/v1/query/`, this.token, this.userUid, { method: 'POST', body: { sql } });
|
|
205
222
|
}
|
|
206
223
|
// ── System ────────────────────────────────────────────────────────────────
|
|
207
224
|
/**
|
|
@@ -210,7 +227,7 @@ export class OtelClient {
|
|
|
210
227
|
* Returns process memory/CPU, disk usage, and per-table row counts.
|
|
211
228
|
*/
|
|
212
229
|
async fetchSystem() {
|
|
213
|
-
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/system/`, this.token);
|
|
230
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/system/`, this.token, this.userUid);
|
|
214
231
|
return (resp.data ?? resp);
|
|
215
232
|
}
|
|
216
233
|
}
|
|
@@ -173,6 +173,8 @@ export declare function useOtelWebSocket(options: {
|
|
|
173
173
|
baseUrl?: string;
|
|
174
174
|
/** JWT token or API key for authentication. */
|
|
175
175
|
token?: string;
|
|
176
|
+
/** Explicit user UID; optional when token contains user.uid/sub. */
|
|
177
|
+
userUid?: string;
|
|
176
178
|
/** Whether to automatically reconnect on disconnect. Default true. */
|
|
177
179
|
autoReconnect?: boolean;
|
|
178
180
|
/** Reconnect delay in ms. Default 3000. */
|
package/lib/otel/hooks/index.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
11
|
import { coreStore } from '../../state/substates/CoreState.js';
|
|
12
|
+
import { buildOtelWebSocketUrl, resolveOtelAuth } from '../auth.js';
|
|
12
13
|
// ── Global 401 handler ──────────────────────────────────────────────
|
|
13
14
|
let _onUnauthorized = null;
|
|
14
15
|
/**
|
|
@@ -35,14 +36,14 @@ export function setOtelOnUnauthorized(cb) {
|
|
|
35
36
|
* handler has been registered via {@link setOtelOnUnauthorized}, the
|
|
36
37
|
* handler is called before the error is thrown.
|
|
37
38
|
*/
|
|
38
|
-
async function otelFetch(url, token, options) {
|
|
39
|
+
async function otelFetch(url, token, userUid, options) {
|
|
40
|
+
const auth = resolveOtelAuth(token, userUid);
|
|
39
41
|
const headers = {
|
|
40
42
|
Accept: 'application/json',
|
|
41
43
|
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
42
44
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
45
|
+
headers['Authorization'] = `Bearer ${auth.token}`;
|
|
46
|
+
headers['X-Datalayer-User-Uid'] = auth.userUid;
|
|
46
47
|
const isPost = options?.method === 'POST';
|
|
47
48
|
if (isPost) {
|
|
48
49
|
headers['Content-Type'] = 'application/json';
|
|
@@ -407,7 +408,7 @@ export function useOtelQuery(options) {
|
|
|
407
408
|
setLoading(true);
|
|
408
409
|
setError(null);
|
|
409
410
|
try {
|
|
410
|
-
const data = await otelFetch(`${baseUrl}/api/otel/v1/query/`, token, {
|
|
411
|
+
const data = await otelFetch(`${baseUrl}/api/otel/v1/query/`, token, undefined, {
|
|
411
412
|
method: 'POST',
|
|
412
413
|
body: { sql },
|
|
413
414
|
});
|
|
@@ -436,7 +437,7 @@ export function useOtelQuery(options) {
|
|
|
436
437
|
* @returns `{ connected, error, close }` – reactive connection state.
|
|
437
438
|
*/
|
|
438
439
|
export function useOtelWebSocket(options) {
|
|
439
|
-
const { baseUrl = coreStore.getState().configuration.otelRunUrl, token, autoReconnect = true, reconnectDelayMs = 3000, callbacks, } = options;
|
|
440
|
+
const { baseUrl = coreStore.getState().configuration.otelRunUrl, token, userUid, autoReconnect = true, reconnectDelayMs = 3000, callbacks, } = options;
|
|
440
441
|
const [connected, setConnected] = useState(false);
|
|
441
442
|
const [error, setError] = useState(null);
|
|
442
443
|
const wsRef = useRef(null);
|
|
@@ -459,23 +460,19 @@ export function useOtelWebSocket(options) {
|
|
|
459
460
|
setConnected(false);
|
|
460
461
|
return;
|
|
461
462
|
}
|
|
462
|
-
// Derive ws(s):// URL from http(s):// baseUrl.
|
|
463
463
|
let wsUrl;
|
|
464
|
-
|
|
465
|
-
wsUrl =
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
else if (baseUrl.startsWith('ws://') || baseUrl.startsWith('wss://')) {
|
|
471
|
-
wsUrl = baseUrl;
|
|
464
|
+
try {
|
|
465
|
+
wsUrl = buildOtelWebSocketUrl({
|
|
466
|
+
baseUrl,
|
|
467
|
+
token,
|
|
468
|
+
userUid,
|
|
469
|
+
});
|
|
472
470
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
471
|
+
catch (err) {
|
|
472
|
+
setError(err?.message ?? 'WebSocket auth error');
|
|
473
|
+
setConnected(false);
|
|
474
|
+
return;
|
|
477
475
|
}
|
|
478
|
-
wsUrl = `${wsUrl.replace(/\/$/, '')}/api/otel/v1/ws?token=${encodeURIComponent(token)}`;
|
|
479
476
|
function connect() {
|
|
480
477
|
const ws = new WebSocket(wsUrl);
|
|
481
478
|
wsRef.current = ws;
|
|
@@ -527,6 +524,6 @@ export function useOtelWebSocket(options) {
|
|
|
527
524
|
wsRef.current = null;
|
|
528
525
|
}
|
|
529
526
|
};
|
|
530
|
-
}, [baseUrl, token, autoReconnect, reconnectDelayMs]);
|
|
527
|
+
}, [baseUrl, token, userUid, autoReconnect, reconnectDelayMs]);
|
|
531
528
|
return { connected, error, close };
|
|
532
529
|
}
|
package/lib/otel/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export type { OtelSpan, OtelLog, OtelMetric, OtelSpanEvent, OtelSpanLink, SignalType, OtelLiveProps, OtelTracesListProps, OtelSpanDetailProps, OtelTimelineProps, OtelLogsListProps, OtelMetricsListProps, OtelSpanTreeProps, OtelSearchBarProps, OtelTimelineRangeSliderProps, OtelQueryRow, OtelQueryResult, } from './types';
|
|
15
15
|
export { toMs, formatDuration, formatTime, serviceColor, kindColor, severityColor, severityVariant, buildSpanTree, flattenSpanTree, } from './utils';
|
|
16
|
+
export { resolveOtelAuth, resolveOtelUserUid, buildOtelWebSocketUrl, } from './auth';
|
|
17
|
+
export type { ResolvedOtelAuth } from './auth';
|
|
16
18
|
export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks';
|
|
17
19
|
export type { OtelWsMessage, OtelWsCallbacks, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './hooks';
|
|
18
20
|
export { OtelClient, createOtelClient } from './client';
|
package/lib/otel/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
// ── Utils ─────────────────────────────────────────────────────────────────────
|
|
6
6
|
export { toMs, formatDuration, formatTime, serviceColor, kindColor, severityColor, severityVariant, buildSpanTree, flattenSpanTree, } from './utils.js';
|
|
7
|
+
// ── Auth helpers ──────────────────────────────────────────────────────────────
|
|
8
|
+
export { resolveOtelAuth, resolveOtelUserUid, buildOtelWebSocketUrl, } from './auth.js';
|
|
7
9
|
// ── Hooks ─────────────────────────────────────────────────────────────────────
|
|
8
10
|
export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks/index.js';
|
|
9
11
|
// ── Client ────────────────────────────────────────────────────────────────────
|
package/lib/otel/types.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export interface OtelMetric {
|
|
|
55
55
|
value: number;
|
|
56
56
|
unit?: string;
|
|
57
57
|
timestamp: string;
|
|
58
|
+
/** Raw nanosecond timestamp preserved from the backend. */
|
|
59
|
+
timestamp_unix_nano?: number;
|
|
58
60
|
attributes?: Record<string, unknown>;
|
|
59
61
|
metric_type?: string;
|
|
60
62
|
}
|
|
@@ -156,7 +158,7 @@ export interface OtelTimelineRangeSliderProps {
|
|
|
156
158
|
formatTick?: (date: Date) => string;
|
|
157
159
|
/** Height of the slider in px. Default 56. */
|
|
158
160
|
height?: number;
|
|
159
|
-
/** Optional histogram data: array of
|
|
161
|
+
/** Optional histogram data: array of objects with `time` (Date) and `count` (number) fields. */
|
|
160
162
|
histogram?: {
|
|
161
163
|
time: Date;
|
|
162
164
|
count: number;
|
|
@@ -7,9 +7,6 @@
|
|
|
7
7
|
* @module otel/OtelMetricsList
|
|
8
8
|
*/
|
|
9
9
|
import React from 'react';
|
|
10
|
-
import type {
|
|
11
|
-
export
|
|
12
|
-
metrics: OtelMetric[];
|
|
13
|
-
loading?: boolean;
|
|
14
|
-
}
|
|
10
|
+
import type { OtelMetricsListProps } from '../types';
|
|
11
|
+
export type { OtelMetricsListProps } from '../types';
|
|
15
12
|
export declare const OtelMetricsList: React.FC<OtelMetricsListProps>;
|
|
@@ -7,6 +7,7 @@ export { OtelLive } from './OtelLive';
|
|
|
7
7
|
export { OtelTracesList } from './OtelTracesList';
|
|
8
8
|
export { OtelLogsList } from './OtelLogsList';
|
|
9
9
|
export { OtelMetricsList } from './OtelMetricsList';
|
|
10
|
+
export type { OtelMetricsListProps } from './OtelMetricsList';
|
|
10
11
|
export { OtelMetricsChart } from './OtelMetricsChart';
|
|
11
12
|
export type { OtelMetricsChartProps } from './OtelMetricsChart';
|
|
12
13
|
export { OtelSearchBar } from './OtelSearchBar';
|
|
@@ -13,7 +13,7 @@ import { ServiceManager } from '@jupyterlab/services';
|
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```typescript
|
|
16
|
-
* const serviceManager = await createDatalayerServiceManager('
|
|
16
|
+
* const serviceManager = await createDatalayerServiceManager('ai-agents-env', 100);
|
|
17
17
|
* await serviceManager.ready;
|
|
18
18
|
* // Use the service manager with notebooks
|
|
19
19
|
* ```
|
|
@@ -20,7 +20,7 @@ import { createRuntime } from '../stateful/runtimes/actions.js';
|
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
22
|
* ```typescript
|
|
23
|
-
* const serviceManager = await createDatalayerServiceManager('
|
|
23
|
+
* const serviceManager = await createDatalayerServiceManager('ai-agents-env', 100);
|
|
24
24
|
* await serviceManager.ready;
|
|
25
25
|
* // Use the service manager with notebooks
|
|
26
26
|
* ```
|
|
@@ -5,6 +5,31 @@
|
|
|
5
5
|
import jwt_decode from 'jwt-decode';
|
|
6
6
|
import { getCookie } from '../../utils/index.js';
|
|
7
7
|
import { ANONYMOUS_USER, ANONYMOUS_USER_TOKEN } from '../../models/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Check if browser localStorage is available and functional.
|
|
10
|
+
* Result is memoized to avoid repeated set/remove probes.
|
|
11
|
+
*/
|
|
12
|
+
let _localStorageAvailable;
|
|
13
|
+
function isLocalStorageAvailable() {
|
|
14
|
+
if (_localStorageAvailable !== undefined) {
|
|
15
|
+
return _localStorageAvailable;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
19
|
+
_localStorageAvailable = false;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const testKey = '__datalayer_ls_test__';
|
|
23
|
+
window.localStorage.setItem(testKey, 'test');
|
|
24
|
+
window.localStorage.removeItem(testKey);
|
|
25
|
+
_localStorageAvailable = true;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
_localStorageAvailable = false;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
8
33
|
export const JWT_DATALAYER_ISSUER = 'https://id.datalayer.run';
|
|
9
34
|
export const DATALAYER_IAM_USER_KEY = '@datalayer/iam:user';
|
|
10
35
|
export const DATALAYER_IAM_TOKEN_KEY = '@datalayer/iam:token';
|
|
@@ -13,7 +38,7 @@ export const JWT_REFRESH_TOKEN_COOKIE_NAME = 'refresh_token';
|
|
|
13
38
|
* Return the user from the local storage.
|
|
14
39
|
*/
|
|
15
40
|
export const getStoredUser = () => {
|
|
16
|
-
if (
|
|
41
|
+
if (!isLocalStorageAvailable()) {
|
|
17
42
|
return ANONYMOUS_USER;
|
|
18
43
|
}
|
|
19
44
|
const user = window.localStorage.getItem(DATALAYER_IAM_USER_KEY);
|
|
@@ -33,7 +58,7 @@ export const getStoredUser = () => {
|
|
|
33
58
|
* Set the user in the local storage.
|
|
34
59
|
*/
|
|
35
60
|
export const storeUser = (user) => {
|
|
36
|
-
if (
|
|
61
|
+
if (!isLocalStorageAvailable()) {
|
|
37
62
|
return;
|
|
38
63
|
}
|
|
39
64
|
if (user) {
|
|
@@ -47,7 +72,7 @@ export const storeUser = (user) => {
|
|
|
47
72
|
* Return the IAM token from the local storage.
|
|
48
73
|
*/
|
|
49
74
|
export const getStoredToken = () => {
|
|
50
|
-
if (
|
|
75
|
+
if (!isLocalStorageAvailable()) {
|
|
51
76
|
return ANONYMOUS_USER_TOKEN;
|
|
52
77
|
}
|
|
53
78
|
const token = window.localStorage.getItem(DATALAYER_IAM_TOKEN_KEY);
|
|
@@ -60,7 +85,7 @@ export const getStoredToken = () => {
|
|
|
60
85
|
* Set the IAM token in the local storage.
|
|
61
86
|
*/
|
|
62
87
|
export const storeToken = (token) => {
|
|
63
|
-
if (
|
|
88
|
+
if (!isLocalStorageAvailable()) {
|
|
64
89
|
return;
|
|
65
90
|
}
|
|
66
91
|
if (token) {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { create } from 'zustand';
|
|
16
16
|
import { persist } from 'zustand/middleware';
|
|
17
|
-
export const useSimpleAuthStore = create()(persist(
|
|
17
|
+
export const useSimpleAuthStore = create()(persist(set => ({
|
|
18
18
|
token: null,
|
|
19
19
|
handle: null,
|
|
20
20
|
setAuth: (token, handle) => set({ token, handle }),
|