@datalayer/core 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/lib/api/constants.d.ts +3 -0
  3. package/lib/api/constants.js +3 -0
  4. package/lib/api/index.d.ts +1 -0
  5. package/lib/api/index.js +1 -0
  6. package/lib/api/otel/index.d.ts +12 -0
  7. package/lib/api/otel/index.js +16 -0
  8. package/lib/api/otel/logs.d.ts +19 -0
  9. package/lib/api/otel/logs.js +43 -0
  10. package/lib/api/otel/metrics.d.ts +31 -0
  11. package/lib/api/otel/metrics.js +65 -0
  12. package/lib/api/otel/query.d.ts +16 -0
  13. package/lib/api/otel/query.js +37 -0
  14. package/lib/api/otel/services.d.ts +39 -0
  15. package/lib/api/otel/services.js +81 -0
  16. package/lib/api/otel/traces.d.ts +24 -0
  17. package/lib/api/otel/traces.js +53 -0
  18. package/lib/api/otel/types.d.ts +112 -0
  19. package/lib/api/otel/types.js +5 -0
  20. package/lib/config/Configuration.d.ts +4 -0
  21. package/lib/hooks/useCache.d.ts +14 -9
  22. package/lib/hooks/useCache.js +28 -0
  23. package/lib/index.d.ts +2 -0
  24. package/lib/index.js +4 -0
  25. package/lib/otel/OtelLive.d.ts +12 -0
  26. package/lib/otel/OtelLive.js +354 -0
  27. package/lib/otel/OtelLogsList.d.ts +11 -0
  28. package/lib/otel/OtelLogsList.js +137 -0
  29. package/lib/otel/OtelMetricsChart.d.ts +22 -0
  30. package/lib/otel/OtelMetricsChart.js +300 -0
  31. package/lib/otel/OtelMetricsList.d.ts +15 -0
  32. package/lib/otel/OtelMetricsList.js +213 -0
  33. package/lib/otel/OtelSearchBar.d.ts +11 -0
  34. package/lib/otel/OtelSearchBar.js +22 -0
  35. package/lib/otel/OtelSpanDetail.d.ts +11 -0
  36. package/lib/otel/OtelSpanDetail.js +172 -0
  37. package/lib/otel/OtelSpanTree.d.ts +11 -0
  38. package/lib/otel/OtelSpanTree.js +176 -0
  39. package/lib/otel/OtelSqlView.d.ts +16 -0
  40. package/lib/otel/OtelSqlView.js +239 -0
  41. package/lib/otel/OtelSystemView.d.ts +15 -0
  42. package/lib/otel/OtelSystemView.js +75 -0
  43. package/lib/otel/OtelTimeline.d.ts +11 -0
  44. package/lib/otel/OtelTimeline.js +101 -0
  45. package/lib/otel/OtelTimelineRangeSlider.d.ts +16 -0
  46. package/lib/otel/OtelTimelineRangeSlider.js +338 -0
  47. package/lib/otel/OtelTracesList.d.ts +13 -0
  48. package/lib/otel/OtelTracesList.js +199 -0
  49. package/lib/otel/hooks.d.ts +172 -0
  50. package/lib/otel/hooks.js +490 -0
  51. package/lib/otel/index.d.ts +25 -0
  52. package/lib/otel/index.js +19 -0
  53. package/lib/otel/types.d.ts +190 -0
  54. package/lib/otel/types.js +5 -0
  55. package/lib/otel/utils.d.ts +33 -0
  56. package/lib/otel/utils.js +181 -0
  57. package/lib/state/storage/IAMStorage.d.ts +2 -1
  58. package/lib/state/substates/CoreState.js +1 -0
  59. package/lib/utils/Jwt.d.ts +42 -0
  60. package/lib/utils/Jwt.js +44 -0
  61. package/lib/utils/index.d.ts +1 -0
  62. package/lib/utils/index.js +1 -0
  63. package/lib/views/iam/SignInSimple.d.ts +38 -0
  64. package/lib/views/iam/SignInSimple.js +80 -0
  65. package/lib/views/iam/index.d.ts +2 -0
  66. package/lib/views/iam/index.js +5 -0
  67. package/lib/views/iam-tokens/IAMTokenEdit.js +53 -4
  68. package/lib/views/iam-tokens/IAMTokens.js +65 -33
  69. package/lib/views/iam-tokens/Tokens.js +63 -31
  70. package/lib/views/index.d.ts +2 -1
  71. package/lib/views/index.js +2 -1
  72. package/lib/views/profile/UserBadge.d.ts +18 -0
  73. package/lib/views/profile/UserBadge.js +101 -0
  74. package/lib/views/profile/index.d.ts +2 -0
  75. package/lib/views/profile/index.js +5 -0
  76. package/package.json +27 -3
@@ -0,0 +1,190 @@
1
+ /**
2
+ * TypeScript types for the OTEL React components.
3
+ *
4
+ * @module otel/types
5
+ */
6
+ /** A single span in a trace. */
7
+ export interface OtelSpan {
8
+ trace_id: string;
9
+ span_id: string;
10
+ parent_span_id?: string;
11
+ span_name: string;
12
+ service_name: string;
13
+ kind: string;
14
+ start_time: string;
15
+ end_time: string;
16
+ duration_ms: number;
17
+ status_code?: string;
18
+ status_message?: string;
19
+ otel_scope_name?: string;
20
+ attributes?: Record<string, unknown>;
21
+ events?: OtelSpanEvent[];
22
+ links?: OtelSpanLink[];
23
+ /** Nested children (computed client-side from parent_span_id). */
24
+ children?: OtelSpan[];
25
+ /** Depth in a span tree (computed client-side). */
26
+ depth?: number;
27
+ }
28
+ /** An event attached to a span. */
29
+ export interface OtelSpanEvent {
30
+ name: string;
31
+ timestamp: string;
32
+ attributes?: Record<string, unknown>;
33
+ }
34
+ /** A link between spans. */
35
+ export interface OtelSpanLink {
36
+ trace_id: string;
37
+ span_id: string;
38
+ attributes?: Record<string, unknown>;
39
+ }
40
+ /** A log record. */
41
+ export interface OtelLog {
42
+ timestamp: string;
43
+ severity_text: string;
44
+ severity_number?: number;
45
+ body: string;
46
+ service_name: string;
47
+ trace_id?: string;
48
+ span_id?: string;
49
+ attributes?: Record<string, unknown>;
50
+ }
51
+ /** A metric data point. */
52
+ export interface OtelMetric {
53
+ metric_name: string;
54
+ service_name: string;
55
+ value: number;
56
+ unit?: string;
57
+ timestamp: string;
58
+ attributes?: Record<string, unknown>;
59
+ metric_type?: string;
60
+ }
61
+ /** Signal type for the Live view tabs. */
62
+ export type SignalType = 'traces' | 'logs' | 'metrics';
63
+ /** Props for the main OtelLive orchestrator component. */
64
+ export interface OtelLiveProps {
65
+ /**
66
+ * OTEL API base URL. When omitted, falls back to `otelRunUrl` from the
67
+ * Datalayer core configuration (defaults to "https://prod1.datalayer.run").
68
+ */
69
+ baseUrl?: string;
70
+ /**
71
+ * WebSocket base URL. When provided, the WebSocket connects directly to this
72
+ * URL instead of deriving it from `baseUrl`. Use this to bypass a dev proxy
73
+ * and connect directly to the OTEL service (e.g. "https://prod1.datalayer.run").
74
+ */
75
+ wsBaseUrl?: string;
76
+ /** Bearer token for authentication. */
77
+ token?: string;
78
+ /** Auto-refresh interval in ms. Set to 0 to disable. Default 5000. */
79
+ autoRefreshMs?: number;
80
+ /** Default signal tab. */
81
+ defaultSignal?: SignalType;
82
+ /** Maximum rows to fetch per signal type. */
83
+ limit?: number;
84
+ /**
85
+ * Callback invoked once with the internal signal setter so a parent
86
+ * component (e.g. a header with generate buttons) can programmatically
87
+ * navigate to a signal tab.
88
+ */
89
+ onSignalRef?: (setter: (s: SignalType) => void) => void;
90
+ }
91
+ /** Props for the traces table. */
92
+ export interface OtelTracesListProps {
93
+ spans: OtelSpan[];
94
+ loading?: boolean;
95
+ selectedSpanId?: string | null;
96
+ onSelectSpan?: (span: OtelSpan) => void;
97
+ }
98
+ /** Props for the span detail panel. */
99
+ export interface OtelSpanDetailProps {
100
+ span: OtelSpan | null;
101
+ /** All spans of the same trace for building the tree. */
102
+ traceSpans?: OtelSpan[];
103
+ onClose?: () => void;
104
+ }
105
+ /** Props for the timeline bar chart. */
106
+ export interface OtelTimelineProps {
107
+ /** All spans belonging to a single trace, sorted by start_time. */
108
+ spans: OtelSpan[];
109
+ /** Height per span bar in px. Default 22. */
110
+ barHeight?: number;
111
+ /** Optional selected span id. */
112
+ selectedSpanId?: string | null;
113
+ /** Called when user clicks a bar. */
114
+ onSelectSpan?: (span: OtelSpan) => void;
115
+ }
116
+ /** Props for the span tree (nested view). */
117
+ export interface OtelSpanTreeProps {
118
+ /** Root spans (with children pre-built). */
119
+ spans: OtelSpan[];
120
+ /** Currently selected span. */
121
+ selectedSpanId?: string | null;
122
+ /** Called when user selects a span node. */
123
+ onSelectSpan?: (span: OtelSpan) => void;
124
+ /** Default expanded depth. Default 2. */
125
+ defaultExpandDepth?: number;
126
+ }
127
+ /** Props for the logs list view. */
128
+ export interface OtelLogsListProps {
129
+ logs: OtelLog[];
130
+ loading?: boolean;
131
+ selectedLogIndex?: number | null;
132
+ onSelectLog?: (log: OtelLog, index: number) => void;
133
+ }
134
+ /** Props for the metrics list view. */
135
+ export interface OtelMetricsListProps {
136
+ metrics: OtelMetric[];
137
+ loading?: boolean;
138
+ }
139
+ /** Props for the timeline range slider. */
140
+ export interface OtelTimelineRangeSliderProps {
141
+ /** Timeline start (left edge). */
142
+ timelineStart: Date;
143
+ /** Timeline end (right edge). */
144
+ timelineEnd: Date;
145
+ /** Current selected range start. */
146
+ selectedStart: Date;
147
+ /** Current selected range end. */
148
+ selectedEnd: Date;
149
+ /** Called continuously while dragging. */
150
+ onRangeChange: (start: Date, end: Date) => void;
151
+ /** Called once when dragging ends. */
152
+ onRangeCommit?: (start: Date, end: Date) => void;
153
+ /** Number of tick marks. Default 8. */
154
+ tickCount?: number;
155
+ /** Format a Date into a tick label string. */
156
+ formatTick?: (date: Date) => string;
157
+ /** Height of the slider in px. Default 56. */
158
+ height?: number;
159
+ /** Optional histogram data: array of { time: Date; count: number } */
160
+ histogram?: {
161
+ time: Date;
162
+ count: number;
163
+ }[];
164
+ }
165
+ /** A single row returned by the SQL query endpoint. */
166
+ export type OtelQueryRow = Record<string, unknown>;
167
+ /** Response shape from POST /api/otel/v1/query/ */
168
+ export interface OtelQueryResult {
169
+ success: boolean;
170
+ data: OtelQueryRow[];
171
+ count: number;
172
+ }
173
+ /** Props for the search / filter bar. */
174
+ export interface OtelSearchBarProps {
175
+ /** Current signal type. */
176
+ signal: SignalType;
177
+ onSignalChange: (signal: SignalType) => void;
178
+ /** List of known service names for filter dropdown. */
179
+ services: string[];
180
+ /** Currently selected service filter (empty = all). */
181
+ selectedService: string;
182
+ onServiceChange: (service: string) => void;
183
+ /** Search / filter query string. */
184
+ query: string;
185
+ onQueryChange: (query: string) => void;
186
+ /** Called when user clicks refresh. */
187
+ onRefresh?: () => void;
188
+ /** Whether data is currently loading. */
189
+ loading?: boolean;
190
+ }
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Helper utilities for OTEL components.
3
+ *
4
+ * @module otel/utils
5
+ */
6
+ import type { OtelSpan } from './types';
7
+ /** Parse an ISO timestamp or nanosecond/microsecond epoch to milliseconds. */
8
+ export declare function toMs(ts: string | number): number;
9
+ /** Format a duration in ms to a human-readable string. */
10
+ export declare function formatDuration(ms: number | undefined | null): string;
11
+ /** Format an ISO timestamp or nanosecond epoch to local time string. */
12
+ export declare function formatTime(ts: string | number | undefined | null): string;
13
+ /** Return a deterministic color for a service name (via simple hash). */
14
+ export declare function serviceColor(serviceName: string): string;
15
+ /** Kind-based color for the span kind indicator dot. */
16
+ export declare function kindColor(kind: string): string;
17
+ /** Severity-based color for log severity labels. */
18
+ export declare function severityColor(severity: string): string;
19
+ /**
20
+ * Map severity text to a Primer `Label` variant string.
21
+ * Used by OtelLogsList for severity badges.
22
+ */
23
+ export declare function severityVariant(severity: string): 'danger' | 'attention' | 'accent' | 'secondary' | 'primary';
24
+ /**
25
+ * Build a span tree from a flat list of spans.
26
+ * Returns root spans (those without a parent or whose parent is not in the list)
27
+ * with `children` populated recursively and `depth` set.
28
+ */
29
+ export declare function buildSpanTree(spans: OtelSpan[]): OtelSpan[];
30
+ /**
31
+ * Flatten a span tree back to a flat list (depth-first) preserving depth info.
32
+ */
33
+ export declare function flattenSpanTree(roots: OtelSpan[]): OtelSpan[];
@@ -0,0 +1,181 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /** Parse an ISO timestamp or nanosecond/microsecond epoch to milliseconds. */
6
+ export function toMs(ts) {
7
+ const n = Number(ts);
8
+ if (!isNaN(n) && n > 1e15)
9
+ return n / 1e6; // nanos → ms
10
+ if (!isNaN(n) && n > 1e12)
11
+ return n / 1e3; // micros → ms
12
+ if (!isNaN(n))
13
+ return n; // already ms
14
+ return new Date(String(ts)).getTime();
15
+ }
16
+ /** Format a duration in ms to a human-readable string. */
17
+ export function formatDuration(ms) {
18
+ if (ms === undefined || ms === null)
19
+ return '—';
20
+ if (ms < 0.001)
21
+ return '<1µs';
22
+ if (ms < 1)
23
+ return `${(ms * 1000).toFixed(0)}µs`;
24
+ if (ms < 1000)
25
+ return `${ms.toFixed(1)}ms`;
26
+ if (ms < 60000)
27
+ return `${(ms / 1000).toFixed(2)}s`;
28
+ return `${(ms / 60000).toFixed(1)}m`;
29
+ }
30
+ /** Format an ISO timestamp or nanosecond epoch to local time string. */
31
+ export function formatTime(ts) {
32
+ if (ts === undefined || ts === null || ts === '')
33
+ return '—';
34
+ try {
35
+ const ms = toMs(ts);
36
+ const d = new Date(ms);
37
+ if (isNaN(d.getTime()))
38
+ return String(ts);
39
+ return d.toLocaleTimeString(undefined, {
40
+ hour: '2-digit',
41
+ minute: '2-digit',
42
+ second: '2-digit',
43
+ fractionalSecondDigits: 3,
44
+ });
45
+ }
46
+ catch {
47
+ return String(ts);
48
+ }
49
+ }
50
+ /** Deterministic color palette for service names. */
51
+ const PALETTE = [
52
+ '#0969da', // blue
53
+ '#1a7f37', // green
54
+ '#cf222e', // red
55
+ '#8250df', // purple
56
+ '#bf8700', // amber
57
+ '#0550ae', // dark blue
58
+ '#116329', // dark green
59
+ '#a40e26', // dark red
60
+ '#6639ba', // dark purple
61
+ '#953800', // brown
62
+ '#0e8a16', // lime
63
+ '#e36209', // orange
64
+ ];
65
+ /** Return a deterministic color for a service name (via simple hash). */
66
+ export function serviceColor(serviceName) {
67
+ let hash = 0;
68
+ for (let i = 0; i < serviceName.length; i++) {
69
+ hash = (hash * 31 + serviceName.charCodeAt(i)) | 0;
70
+ }
71
+ return PALETTE[Math.abs(hash) % PALETTE.length];
72
+ }
73
+ /** Kind-based color for the span kind indicator dot. */
74
+ export function kindColor(kind) {
75
+ switch (kind?.toUpperCase()) {
76
+ case 'SERVER':
77
+ return '#0969da';
78
+ case 'CLIENT':
79
+ return '#1a7f37';
80
+ case 'PRODUCER':
81
+ return '#8250df';
82
+ case 'CONSUMER':
83
+ return '#bf8700';
84
+ case 'INTERNAL':
85
+ default:
86
+ return '#656d76';
87
+ }
88
+ }
89
+ /** Severity-based color for log severity labels. */
90
+ export function severityColor(severity) {
91
+ switch (severity?.toUpperCase()) {
92
+ case 'ERROR':
93
+ case 'FATAL':
94
+ return 'danger.fg';
95
+ case 'WARN':
96
+ case 'WARNING':
97
+ return 'attention.fg';
98
+ case 'INFO':
99
+ return 'accent.fg';
100
+ case 'DEBUG':
101
+ case 'TRACE':
102
+ return 'fg.muted';
103
+ default:
104
+ return 'fg.default';
105
+ }
106
+ }
107
+ /**
108
+ * Map severity text to a Primer `Label` variant string.
109
+ * Used by OtelLogsList for severity badges.
110
+ */
111
+ export function severityVariant(severity) {
112
+ switch (severity?.toUpperCase()) {
113
+ case 'ERROR':
114
+ case 'FATAL':
115
+ return 'danger';
116
+ case 'WARN':
117
+ case 'WARNING':
118
+ return 'attention';
119
+ case 'INFO':
120
+ return 'accent';
121
+ case 'DEBUG':
122
+ case 'TRACE':
123
+ return 'secondary';
124
+ default:
125
+ return 'primary';
126
+ }
127
+ }
128
+ /**
129
+ * Build a span tree from a flat list of spans.
130
+ * Returns root spans (those without a parent or whose parent is not in the list)
131
+ * with `children` populated recursively and `depth` set.
132
+ */
133
+ export function buildSpanTree(spans) {
134
+ const byId = new Map();
135
+ const enriched = spans.map(s => ({
136
+ ...s,
137
+ children: [],
138
+ depth: 0,
139
+ }));
140
+ for (const s of enriched) {
141
+ byId.set(s.span_id, s);
142
+ }
143
+ const roots = [];
144
+ for (const s of enriched) {
145
+ if (s.parent_span_id && byId.has(s.parent_span_id)) {
146
+ const parent = byId.get(s.parent_span_id);
147
+ parent.children = parent.children ?? [];
148
+ parent.children.push(s);
149
+ }
150
+ else {
151
+ roots.push(s);
152
+ }
153
+ }
154
+ // Set depths
155
+ function setDepth(node, depth) {
156
+ node.depth = depth;
157
+ for (const child of node.children ?? []) {
158
+ setDepth(child, depth + 1);
159
+ }
160
+ }
161
+ for (const root of roots) {
162
+ setDepth(root, 0);
163
+ }
164
+ return roots;
165
+ }
166
+ /**
167
+ * Flatten a span tree back to a flat list (depth-first) preserving depth info.
168
+ */
169
+ export function flattenSpanTree(roots) {
170
+ const result = [];
171
+ function walk(node) {
172
+ result.push(node);
173
+ for (const child of node.children ?? []) {
174
+ walk(child);
175
+ }
176
+ }
177
+ for (const root of roots) {
178
+ walk(root);
179
+ }
180
+ return result;
181
+ }
@@ -9,7 +9,8 @@ export type IJWTToken = {
9
9
  iss: string;
10
10
  jti: number;
11
11
  roles: Array<string>;
12
- sub: IUser;
12
+ sub: string;
13
+ user: IUser;
13
14
  };
14
15
  /**
15
16
  * Return the user from the local storage.
@@ -23,6 +23,7 @@ let initialConfiguration = {
23
23
  aiagentsRunUrl: 'https://prod1.datalayer.run',
24
24
  aiinferenceRunUrl: 'https://prod1.datalayer.run',
25
25
  mcpserversRunUrl: 'https://prod1.datalayer.run',
26
+ otelRunUrl: 'https://prod1.datalayer.run',
26
27
  growthRunUrl: 'https://prod1.datalayer.run',
27
28
  inboundsRunUrl: 'https://prod1.datalayer.run',
28
29
  successRunUrl: 'https://prod1.datalayer.run',
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Lightweight JWT payload utilities.
3
+ *
4
+ * Decodes a JWT without verification (client-side display only).
5
+ * Never use for security-critical checks.
6
+ */
7
+ export interface DatalayerJwtUser {
8
+ id: string;
9
+ uid: string;
10
+ handle: string;
11
+ email: string;
12
+ firstName: string;
13
+ lastName: string;
14
+ avatarUrl: string;
15
+ roles: string[];
16
+ }
17
+ /** Full Datalayer JWT payload shape. */
18
+ export interface DatalayerJwtPayload {
19
+ jti: string;
20
+ iss: string;
21
+ iat: number;
22
+ exp: number;
23
+ sub: string;
24
+ user: DatalayerJwtUser;
25
+ /** Legacy top-level roles array (some tokens). */
26
+ roles?: string[];
27
+ }
28
+ /**
29
+ * Decode the payload of a JWT without verifying the signature.
30
+ * Returns `null` on any error (malformed token, invalid base64, etc.).
31
+ */
32
+ export declare function parseJwtPayload<T = DatalayerJwtPayload>(token: string): T | null;
33
+ /**
34
+ * Extract the Datalayer user object from a JWT, returning `null` if the
35
+ * token is missing or cannot be decoded.
36
+ */
37
+ export declare function getDatalayerJwtUser(token: string | null | undefined): DatalayerJwtUser | null;
38
+ /**
39
+ * Format a human-readable display name from a JWT user.
40
+ * Prefers "FirstName LastName", falls back to handle.
41
+ */
42
+ export declare function getDatalayerDisplayName(user: DatalayerJwtUser | null | undefined, fallback?: string): string;
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ // ── Utilities ─────────────────────────────────────────────────────
6
+ /**
7
+ * Decode the payload of a JWT without verifying the signature.
8
+ * Returns `null` on any error (malformed token, invalid base64, etc.).
9
+ */
10
+ export function parseJwtPayload(token) {
11
+ try {
12
+ const parts = token.split('.');
13
+ if (parts.length < 2)
14
+ return null;
15
+ // Base64Url → Base64 → JSON
16
+ const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
17
+ const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
18
+ const json = atob(padded);
19
+ return JSON.parse(json);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ /**
26
+ * Extract the Datalayer user object from a JWT, returning `null` if the
27
+ * token is missing or cannot be decoded.
28
+ */
29
+ export function getDatalayerJwtUser(token) {
30
+ if (!token)
31
+ return null;
32
+ const payload = parseJwtPayload(token);
33
+ return payload?.user ?? null;
34
+ }
35
+ /**
36
+ * Format a human-readable display name from a JWT user.
37
+ * Prefers "FirstName LastName", falls back to handle.
38
+ */
39
+ export function getDatalayerDisplayName(user, fallback = '') {
40
+ if (!user)
41
+ return fallback;
42
+ const full = `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim();
43
+ return full || user.handle || fallback;
44
+ }
@@ -11,6 +11,7 @@ export * from './Env';
11
11
  export * from './File';
12
12
  export * from './Format';
13
13
  export * from './Ids';
14
+ export * from './Jwt';
14
15
  export * from './Jupyter';
15
16
  export * from './Lazy';
16
17
  export * from './Msc';
@@ -15,6 +15,7 @@ export * from './Env';
15
15
  export * from './File';
16
16
  export * from './Format';
17
17
  export * from './Ids';
18
+ export * from './Jwt';
18
19
  export * from './Jupyter';
19
20
  export * from './Lazy';
20
21
  export * from './Msc';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * SignInSimple – Generic handle + password sign-in form.
3
+ *
4
+ * Posts to `loginUrl` (default `/api/iam/v1/login`) with
5
+ * `{ handle, password }`, then calls `onSignIn(token, handle)` on
6
+ * success so the caller can persist credentials as needed.
7
+ *
8
+ * @module views/signin
9
+ */
10
+ import React from 'react';
11
+ export interface SignInSimpleProps {
12
+ /**
13
+ * Called after a successful login with the JWT and the user handle.
14
+ * Typically used to store credentials in a Zustand / context store.
15
+ */
16
+ onSignIn: (token: string, handle: string) => void;
17
+ /**
18
+ * Login endpoint. Defaults to `/api/iam/v1/login`.
19
+ * The endpoint must accept `POST { handle, password }` and return
20
+ * `{ success: boolean; token?: string; message?: string }`.
21
+ */
22
+ loginUrl?: string;
23
+ /**
24
+ * Optional heading text. Defaults to `"Datalayer OTEL"`.
25
+ */
26
+ title?: string;
27
+ /**
28
+ * Optional subtitle / description.
29
+ */
30
+ description?: string;
31
+ /**
32
+ * Leading icon element rendered next to the title.
33
+ * Defaults to `<TelescopeIcon size={24} />`.
34
+ */
35
+ leadingIcon?: React.ReactNode;
36
+ }
37
+ export declare const SignInSimple: React.FC<SignInSimpleProps>;
38
+ export default SignInSimple;
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * SignInSimple – Generic handle + password sign-in form.
8
+ *
9
+ * Posts to `loginUrl` (default `/api/iam/v1/login`) with
10
+ * `{ handle, password }`, then calls `onSignIn(token, handle)` on
11
+ * success so the caller can persist credentials as needed.
12
+ *
13
+ * @module views/signin
14
+ */
15
+ import { useState, useCallback } from 'react';
16
+ import { Box, Button, FormControl, Heading, Text, TextInput, } from '@primer/react';
17
+ import { EyeIcon, EyeClosedIcon, TelescopeIcon } from '@primer/octicons-react';
18
+ // ── Component ────────────────────────────────────────────────────────
19
+ export const SignInSimple = ({ onSignIn, loginUrl = '/api/iam/v1/login', title = 'Datalayer OTEL', description = 'Sign in to access the observability dashboard.', leadingIcon = _jsx(TelescopeIcon, { size: 24 }), }) => {
20
+ const [handle, setHandle] = useState('');
21
+ const [password, setPassword] = useState('');
22
+ const [showPassword, setShowPassword] = useState(false);
23
+ const [loading, setLoading] = useState(false);
24
+ const [error, setError] = useState(null);
25
+ const submit = useCallback(async () => {
26
+ if (!handle || !password || loading)
27
+ return;
28
+ setLoading(true);
29
+ setError(null);
30
+ try {
31
+ const resp = await fetch(loginUrl, {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ handle, password }),
35
+ });
36
+ if (!resp.ok) {
37
+ throw new Error(`HTTP ${resp.status}`);
38
+ }
39
+ const data = await resp.json();
40
+ if (data.success && data.token) {
41
+ onSignIn(data.token, handle);
42
+ }
43
+ else {
44
+ setError(data.message || 'Invalid username or password.');
45
+ }
46
+ }
47
+ catch (err) {
48
+ setError(err instanceof Error ? err.message : String(err));
49
+ }
50
+ finally {
51
+ setLoading(false);
52
+ }
53
+ }, [handle, password, loading, loginUrl, onSignIn]);
54
+ const handleKeyDown = useCallback((e) => {
55
+ if (e.key === 'Enter')
56
+ submit();
57
+ }, [submit]);
58
+ return (_jsx(Box, { sx: {
59
+ display: 'flex',
60
+ alignItems: 'center',
61
+ justifyContent: 'center',
62
+ height: '100vh',
63
+ bg: 'canvas.default',
64
+ color: 'fg.default',
65
+ }, children: _jsxs(Box, { sx: {
66
+ width: 360,
67
+ p: 4,
68
+ borderRadius: 2,
69
+ border: '1px solid',
70
+ borderColor: 'border.default',
71
+ bg: 'canvas.subtle',
72
+ }, children: [_jsxs(Box, { sx: {
73
+ display: 'flex',
74
+ alignItems: 'center',
75
+ gap: 2,
76
+ mb: 4,
77
+ justifyContent: 'center',
78
+ }, children: [leadingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: title })] }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mb: 3, textAlign: 'center' }, children: description }), _jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Username" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your username", value: handle, onChange: e => setHandle(e.target.value), onKeyDown: handleKeyDown })] }), _jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Password" }), _jsx(TextInput, { block: true, placeholder: "Your password", type: showPassword ? 'text' : 'password', value: password, onChange: e => setPassword(e.target.value), onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => setShowPassword(!showPassword), icon: showPassword ? EyeClosedIcon : EyeIcon, "aria-label": showPassword ? 'Hide password' : 'Show password', sx: { color: 'var(--fgColor-muted)' } }) })] }), error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 1, mb: 3, display: 'block' }, children: error })), _jsx(Button, { variant: "primary", block: true, disabled: loading || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign in' })] }) }));
79
+ };
80
+ export default SignInSimple;
@@ -0,0 +1,2 @@
1
+ export { SignInSimple } from './SignInSimple';
2
+ export type { SignInSimpleProps } from './SignInSimple';
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export { SignInSimple } from './SignInSimple';