@datalayer/core 1.2.17 → 1.2.19
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/cli-login.js +5 -51
- package/lib/components/anonymous/AnonymousKeyExpired.js +1 -15
- package/lib/components/anonymous/AnonymousKeyTimer.js +5 -15
- package/lib/components/auth/SignIn.js +2 -2
- package/lib/components/avatars/UserAvatar.js +3 -27
- package/lib/components/billing/BillingEntitySelect.js +6 -48
- package/lib/components/checkout/StripeCheckout.js +33 -217
- package/lib/components/contents/ContentSourceCard.js +2 -11
- package/lib/components/contents/HomeFolderObjectsBrowser.d.ts +1 -1
- package/lib/components/contents/HomeFolderObjectsBrowser.js +11 -35
- package/lib/components/display/CenteredSpinner.js +1 -6
- package/lib/components/display/DatalayerBox.js +2 -14
- package/lib/components/flashes/FlashClosable.js +2 -17
- package/lib/components/mcp/ClientBadge.js +1 -1
- package/lib/components/mcp/McpErrorBlankslate.js +1 -1
- package/lib/components/mcp/ScopeList.js +1 -1
- package/lib/components/notebooks/JupyterNotebook.js +1 -2
- package/lib/components/principal/Principal.js +2 -10
- package/lib/components/principal/PrincipalAppearance.js +19 -63
- package/lib/components/principal/PrincipalAvatar.js +5 -25
- package/lib/components/principal/PrincipalBadge.js +3 -13
- package/lib/components/principal/PrincipalBanner.js +4 -32
- package/lib/components/principal/PrincipalDetailsOverlay.js +7 -23
- package/lib/components/principal/PrincipalHoverCard.js +15 -18
- package/lib/components/principal/PrincipalSwitcherMenu.js +15 -83
- package/lib/components/progress/ConsumptionBar.js +1 -1
- package/lib/components/sharing/ShareAccessComponent.js +21 -229
- package/lib/components/sharing/SharingEditor.js +1 -1
- package/lib/components/spaces/SpaceDetailsOverlay.js +4 -35
- package/lib/components/spaces/SpaceDisplay.js +1 -8
- package/lib/components/storage/ContentsBrowser.js +3 -26
- package/lib/components/storage/ContentsTable.js +4 -23
- package/lib/components/students/StudentResultCircle.js +7 -29
- package/lib/components/timeline/Timeline.js +4 -36
- package/lib/hooks/layouts/LayoutBackdrop.js +2 -1
- package/lib/hooks/layouts/LayoutScreencapture.js +1 -1
- package/lib/hooks/libraryApp.d.ts +6 -1
- package/lib/hooks/libraryApp.js +10 -1
- package/lib/hooks/useAuthorization.js +6 -1
- package/lib/mocks/components/FlashMock.js +3 -8
- package/lib/mocks/views/DashboardMock.js +6 -72
- package/lib/mocks/views/FormMock.js +3 -2
- package/lib/mocks/views/TableMock.js +3 -2
- package/lib/mocks/views/WipMock.js +2 -1
- package/lib/models/Person.d.ts +9 -0
- package/lib/models/Person.js +23 -0
- package/lib/otel/index.d.ts +4 -0
- package/lib/otel/index.js +3 -0
- package/lib/otel/live/filter.d.ts +15 -0
- package/lib/otel/live/filter.js +16 -0
- package/lib/otel/live/index.d.ts +10 -0
- package/lib/otel/live/index.js +14 -0
- package/lib/otel/live/tracer.d.ts +94 -0
- package/lib/otel/live/tracer.js +223 -0
- package/lib/otel/live/useOtelLiveSpans.d.ts +6 -0
- package/lib/otel/live/useOtelLiveSpans.js +22 -0
- package/lib/otel/types.d.ts +17 -0
- package/lib/otel/views/OtelLive.js +4 -62
- package/lib/otel/views/OtelLiveSpans.d.ts +52 -0
- package/lib/otel/views/OtelLiveSpans.js +114 -0
- package/lib/otel/views/OtelLogsList.js +13 -64
- package/lib/otel/views/OtelMetricsChart.js +4 -19
- package/lib/otel/views/OtelMetricsList.js +8 -77
- package/lib/otel/views/OtelSearchBar.js +3 -12
- package/lib/otel/views/OtelSpanDetail.d.ts +6 -0
- package/lib/otel/views/OtelSpanDetail.js +31 -81
- package/lib/otel/views/OtelSpanTree.js +7 -57
- package/lib/otel/views/OtelSqlView.js +5 -99
- package/lib/otel/views/OtelSystemView.js +4 -34
- package/lib/otel/views/OtelTimeline.js +7 -37
- package/lib/otel/views/OtelTimelineRangeSlider.js +7 -140
- package/lib/otel/views/OtelTracesList.js +39 -87
- package/lib/otel/views/index.d.ts +3 -0
- package/lib/otel/views/index.js +2 -0
- package/lib/portable/index.d.ts +8 -0
- package/lib/portable/index.js +12 -0
- package/lib/state/substates/NavigationState.d.ts +6 -4
- package/lib/state/substates/NavigationState.js +6 -5
- package/lib/views/api-keys/APIKeyEdit.js +2 -11
- package/lib/views/api-keys/APIKeyNew.js +3 -19
- package/lib/views/api-keys/APIKeys.js +6 -11
- package/lib/views/api-keys/APIKeysStandalone.js +1 -1
- package/lib/views/datasources/DatasourceDetail.d.ts +1 -1
- package/lib/views/datasources/DatasourceDetail.js +24 -17
- package/lib/views/datasources/DatasourceNew.js +2 -6
- package/lib/views/datasources/Datasources.js +2 -14
- package/lib/views/iam/SignInSimple.js +8 -64
- package/lib/views/mcp/AdmittedClients.js +3 -16
- package/lib/views/mcp/AlertDestinations.js +2 -10
- package/lib/views/mcp/AlertRules.js +4 -9
- package/lib/views/mcp/ApprovalQueue.js +4 -21
- package/lib/views/mcp/AuditLog.js +7 -44
- package/lib/views/mcp/ConnectedAgents.js +1 -6
- package/lib/views/mcp/EnterpriseConsole.js +10 -23
- package/lib/views/mcp/IdentityProviders.js +7 -31
- package/lib/views/mcp/McpDashboard.js +14 -30
- package/lib/views/mcp/McpHome.js +7 -54
- package/lib/views/mcp/McpObservability.js +13 -85
- package/lib/views/mcp/NotebookRuns.js +5 -12
- package/lib/views/mcp/OrganizationPolicy.js +3 -9
- package/lib/views/mcp/OrganizationUsage.js +4 -11
- package/lib/views/mcp/PersonalPolicy.js +3 -3
- package/lib/views/mcp/Policies.js +5 -46
- package/lib/views/mcp/PolicyForm.js +1 -9
- package/lib/views/mcp/PolicyHistory.js +2 -8
- package/lib/views/mcp/RunDetail.js +3 -25
- package/lib/views/mcp/ScimProvisioning.js +3 -15
- package/lib/views/mcp/ServiceAgents.js +4 -17
- package/lib/views/mcp/TeamPolicies.js +7 -19
- package/lib/views/mcp/ToolAccess.js +15 -22
- package/lib/views/mcp/TraceTimeline.js +8 -23
- package/lib/views/otel/DashboardView.js +2 -2
- package/lib/views/otel/LogsView.js +2 -2
- package/lib/views/otel/MetricsView.js +2 -2
- package/lib/views/otel/OtelHeader.js +6 -40
- package/lib/views/otel/SqlView.js +2 -2
- package/lib/views/otel/SystemView.js +2 -2
- package/lib/views/otel/TracesView.js +2 -2
- package/lib/views/profile/UserBadge.js +6 -98
- package/lib/views/secrets/SecretEdit.js +2 -11
- package/lib/views/secrets/SecretNew.js +1 -1
- package/package.json +1 -1
|
@@ -14,11 +14,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
* @module otel/OtelTracesList
|
|
15
15
|
*/
|
|
16
16
|
import { useMemo, useCallback, useState } from 'react';
|
|
17
|
-
import { Box
|
|
17
|
+
import { Box } from '@datalayer/primer-addons';
|
|
18
|
+
import { Text, Label, Spinner } from '@primer/react';
|
|
18
19
|
import { Blankslate } from '@primer/react/experimental';
|
|
19
20
|
import { TelescopeIcon, ChevronRightIcon, ChevronDownIcon, } from '@primer/octicons-react';
|
|
20
21
|
import { formatDuration, formatTime, buildSpanTree } from '../utils.js';
|
|
21
22
|
const GRID_COLS = '140px 1fr 160px 90px';
|
|
23
|
+
/** Narrower: for a panel beside other content. */
|
|
24
|
+
const COMPACT_GRID_COLS = '96px minmax(0, 1fr) 96px 64px';
|
|
22
25
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
23
26
|
/** Extract gen_ai token counts from span attributes. */
|
|
24
27
|
function getTokenUsage(span) {
|
|
@@ -35,9 +38,13 @@ function getTokenUsage(span) {
|
|
|
35
38
|
return null;
|
|
36
39
|
return { input, output };
|
|
37
40
|
}
|
|
38
|
-
export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }) => {
|
|
39
|
-
//
|
|
40
|
-
const [
|
|
41
|
+
export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, emptyText, compact = false, defaultExpanded = false, renderSpanMark, describeSpan, }) => {
|
|
42
|
+
// The span_ids unfolded (or, when every span starts unfolded, folded).
|
|
43
|
+
const [toggled, setToggled] = useState(new Set());
|
|
44
|
+
const expanded = useMemo(() => ({
|
|
45
|
+
has: (spanId) => defaultExpanded ? !toggled.has(spanId) : toggled.has(spanId),
|
|
46
|
+
}), [toggled, defaultExpanded]);
|
|
47
|
+
const gridCols = compact ? COMPACT_GRID_COLS : GRID_COLS;
|
|
41
48
|
// Build the span tree from flat input
|
|
42
49
|
const roots = useMemo(() => buildSpanTree(spans), [spans]);
|
|
43
50
|
// Flatten visible rows based on which nodes are expanded
|
|
@@ -58,7 +65,7 @@ export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }
|
|
|
58
65
|
return rows;
|
|
59
66
|
}, [roots, expanded]);
|
|
60
67
|
const toggleExpand = useCallback((spanId) => {
|
|
61
|
-
|
|
68
|
+
setToggled(prev => {
|
|
62
69
|
const next = new Set(prev);
|
|
63
70
|
if (next.has(spanId)) {
|
|
64
71
|
next.delete(spanId);
|
|
@@ -70,23 +77,12 @@ export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }
|
|
|
70
77
|
});
|
|
71
78
|
}, []);
|
|
72
79
|
if (loading) {
|
|
73
|
-
return (_jsx(Box, {
|
|
80
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", p: 5, children: _jsx(Spinner, { size: "medium" }) }));
|
|
74
81
|
}
|
|
75
82
|
if (spans.length === 0) {
|
|
76
|
-
return (_jsxs(Blankslate, { children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: 24 }) }), _jsx(Blankslate.Heading, { children: "No traces found" }), _jsx(Blankslate.Description, { children:
|
|
83
|
+
return (_jsxs(Blankslate, { children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: 24 }) }), _jsx(Blankslate.Heading, { children: "No traces found" }), _jsx(Blankslate.Description, { children: emptyText ?? 'Send some telemetry data first.' })] }));
|
|
77
84
|
}
|
|
78
|
-
return (_jsxs(Box, {
|
|
79
|
-
display: 'grid',
|
|
80
|
-
gridTemplateColumns: GRID_COLS,
|
|
81
|
-
gap: 2,
|
|
82
|
-
px: 3,
|
|
83
|
-
position: 'sticky',
|
|
84
|
-
top: 0,
|
|
85
|
-
bg: 'canvas.subtle',
|
|
86
|
-
zIndex: 1,
|
|
87
|
-
borderBottom: '2px solid',
|
|
88
|
-
borderColor: 'border.default',
|
|
89
|
-
}, children: ['Time', 'Message', 'Scope', 'Duration'].map(h => (_jsx(Text, { sx: {
|
|
85
|
+
return (_jsxs(Box, { width: "100%", flex: 1, minHeight: 0, overflow: "auto", children: [_jsx(Box, { display: "grid", gridTemplateColumns: gridCols, gap: 2, px: 3, position: "sticky", top: 0, bg: "canvas.subtle", zIndex: 1, borderBottom: "2px solid", borderColor: "border.default", children: ['Time', 'Message', 'Scope', 'Duration'].map(h => (_jsx(Text, { sx: {
|
|
90
86
|
fontWeight: 'bold',
|
|
91
87
|
fontSize: 0,
|
|
92
88
|
color: 'fg.muted',
|
|
@@ -97,89 +93,45 @@ export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }
|
|
|
97
93
|
}, children: h }, h))) }), visibleRows.map(({ span, depth, hasChildren }, idx) => {
|
|
98
94
|
const isSelected = selectedSpanId === span.span_id;
|
|
99
95
|
const isExpanded = expanded.has(span.span_id);
|
|
100
|
-
const indent = depth * 20;
|
|
101
|
-
|
|
96
|
+
const indent = depth * (compact ? 12 : 20);
|
|
97
|
+
const description = describeSpan?.(span);
|
|
98
|
+
return (_jsxs(Box, { "data-otel-span": span.span_id, "data-otel-span-name": span.span_name, onClick: () => {
|
|
102
99
|
if (hasChildren) {
|
|
103
100
|
toggleExpand(span.span_id);
|
|
104
101
|
}
|
|
105
102
|
onSelectSpan?.(span);
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
cursor: 'pointer',
|
|
113
|
-
bg: isSelected
|
|
114
|
-
? 'accent.subtle'
|
|
115
|
-
: depth > 0
|
|
116
|
-
? 'canvas.inset'
|
|
117
|
-
: 'canvas.default',
|
|
118
|
-
borderBottom: '1px solid',
|
|
119
|
-
borderColor: 'border.muted',
|
|
120
|
-
':hover': {
|
|
121
|
-
bg: isSelected ? 'accent.subtle' : 'canvas.subtle',
|
|
122
|
-
},
|
|
123
|
-
}, children: [_jsx(Text, { sx: {
|
|
124
|
-
fontSize: 1,
|
|
103
|
+
}, display: "grid", gridTemplateColumns: gridCols, gap: 2, px: 3, py: "5px", cursor: "pointer", bg: isSelected
|
|
104
|
+
? 'accent.subtle'
|
|
105
|
+
: depth > 0
|
|
106
|
+
? 'canvas.inset'
|
|
107
|
+
: 'canvas.default', borderBottom: "1px solid", borderColor: "border.muted", hover: { bg: isSelected ? 'accent.subtle' : 'canvas.subtle' }, children: [_jsx(Text, { sx: {
|
|
108
|
+
fontSize: compact ? 0 : 1,
|
|
125
109
|
fontFamily: 'mono',
|
|
126
110
|
color: 'fg.muted',
|
|
127
111
|
whiteSpace: 'nowrap',
|
|
128
112
|
lineHeight: '22px',
|
|
129
|
-
}, children: formatTime(span.start_time) }), _jsxs(Box, {
|
|
130
|
-
display: 'flex',
|
|
131
|
-
alignItems: 'center',
|
|
132
|
-
gap: 1,
|
|
133
|
-
overflow: 'hidden',
|
|
134
|
-
lineHeight: '22px',
|
|
135
|
-
pl: `${indent}px`,
|
|
136
|
-
}, children: [hasChildren ? (_jsx(Box, { sx: {
|
|
137
|
-
flexShrink: 0,
|
|
138
|
-
color: 'fg.muted',
|
|
139
|
-
display: 'flex',
|
|
140
|
-
alignItems: 'center',
|
|
141
|
-
}, children: isExpanded ? (_jsx(ChevronDownIcon, { size: 14 })) : (_jsx(ChevronRightIcon, { size: 14 })) })) : depth > 0 ? (
|
|
113
|
+
}, children: formatTime(span.start_time) }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 1, overflow: "hidden", lineHeight: "22px", pl: `${indent}px`, children: [hasChildren ? (_jsx(Box, { flexShrink: 0, color: "fg.muted", display: "flex", alignItems: "center", children: isExpanded ? (_jsx(ChevronDownIcon, { size: 14 })) : (_jsx(ChevronRightIcon, { size: 14 })) })) : depth > 0 ? (
|
|
142
114
|
/* Connector dash for child leaves */
|
|
143
|
-
_jsx(Box, { sx: {
|
|
144
|
-
flexShrink: 0,
|
|
145
|
-
width: '14px',
|
|
146
|
-
display: 'flex',
|
|
147
|
-
alignItems: 'center',
|
|
148
|
-
justifyContent: 'center',
|
|
149
|
-
color: 'fg.subtle',
|
|
150
|
-
fontSize: 0,
|
|
151
|
-
}, children: "\u2500" })) : null, _jsx(Text, { sx: {
|
|
115
|
+
_jsx(Box, { flexShrink: 0, width: "14px", display: "flex", alignItems: "center", justifyContent: "center", color: "fg.subtle", fontSize: 0, children: "\u2500" })) : null, renderSpanMark?.(span), _jsx(Text, { sx: {
|
|
152
116
|
fontSize: 1,
|
|
153
117
|
overflow: 'hidden',
|
|
154
118
|
textOverflow: 'ellipsis',
|
|
155
119
|
whiteSpace: 'nowrap',
|
|
156
|
-
|
|
120
|
+
flexShrink: description ? 0 : 1,
|
|
121
|
+
maxWidth: description ? '60%' : undefined,
|
|
122
|
+
}, title: span.span_name, children: span.span_name }), span.in_progress && (_jsx(Label, { variant: "attention", size: "small", children: "running" })), span.status_code === 'ERROR' && (_jsx(Label, { variant: "danger", size: "small", children: "ERROR" })), (() => {
|
|
157
123
|
const usage = getTokenUsage(span);
|
|
158
124
|
if (!usage)
|
|
159
125
|
return null;
|
|
160
|
-
return (_jsxs(Box, { sx: {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
fontFamily: 'mono',
|
|
170
|
-
color: 'fg.muted',
|
|
171
|
-
flexShrink: 0,
|
|
172
|
-
lineHeight: '16px',
|
|
173
|
-
}, children: [usage.input != null && (_jsxs(Box, { sx: {
|
|
174
|
-
display: 'inline-flex',
|
|
175
|
-
alignItems: 'center',
|
|
176
|
-
gap: '1px',
|
|
177
|
-
}, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { sx: {
|
|
178
|
-
display: 'inline-flex',
|
|
179
|
-
alignItems: 'center',
|
|
180
|
-
gap: '1px',
|
|
181
|
-
}, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
|
|
182
|
-
})(), span.otel_scope_name && (_jsx(Label, { size: "small", variant: "secondary", sx: { flexShrink: 0, fontSize: '10px' }, children: span.otel_scope_name }))] }), _jsx(Text, { sx: {
|
|
126
|
+
return (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2px", border: "1px solid", borderColor: "border.default", borderRadius: 2, px: 1, fontSize: "10px", fontFamily: "mono", color: "fg.muted", flexShrink: 0, lineHeight: "16px", children: [usage.input != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "1px", children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
|
|
127
|
+
})(), description && (_jsx(Text, { sx: {
|
|
128
|
+
fontSize: 1,
|
|
129
|
+
color: 'fg.muted',
|
|
130
|
+
overflow: 'hidden',
|
|
131
|
+
textOverflow: 'ellipsis',
|
|
132
|
+
whiteSpace: 'nowrap',
|
|
133
|
+
minWidth: 0,
|
|
134
|
+
}, title: description, "data-otel-span-description": "", children: description })), span.otel_scope_name && !compact && (_jsx(Label, { size: "small", variant: "secondary", sx: { flexShrink: 0, fontSize: '10px' }, children: span.otel_scope_name }))] }), _jsx(Text, { sx: {
|
|
183
135
|
fontSize: 1,
|
|
184
136
|
color: 'fg.muted',
|
|
185
137
|
overflow: 'hidden',
|
|
@@ -194,6 +146,6 @@ export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }
|
|
|
194
146
|
color: span.duration_ms != null && span.duration_ms > 1000
|
|
195
147
|
? 'danger.fg'
|
|
196
148
|
: 'fg.default',
|
|
197
|
-
}, children: formatDuration(span.duration_ms) })] }, `${span.trace_id}-${span.span_id}-${idx}`));
|
|
149
|
+
}, children: span.in_progress ? '…' : formatDuration(span.duration_ms) })] }, `${span.trace_id}-${span.span_id}-${idx}`));
|
|
198
150
|
})] }));
|
|
199
151
|
};
|
|
@@ -14,6 +14,9 @@ export { OtelSearchBar } from './OtelSearchBar';
|
|
|
14
14
|
export { OtelSpanDetail } from './OtelSpanDetail';
|
|
15
15
|
export { OtelSpanTree } from './OtelSpanTree';
|
|
16
16
|
export { OtelTimeline } from './OtelTimeline';
|
|
17
|
+
export { OtelLiveSpans, DEFAULT_SPAN_FACETS, filterSpansByFacets, } from './OtelLiveSpans';
|
|
18
|
+
export type { OtelLiveSpansProps, OtelSpanFacet } from './OtelLiveSpans';
|
|
19
|
+
export { unfoldedValue } from './OtelSpanDetail';
|
|
17
20
|
export { OtelTimelineRangeSlider } from './OtelTimelineRangeSlider';
|
|
18
21
|
export { OtelSqlView } from './OtelSqlView';
|
|
19
22
|
export type { OtelSqlViewProps } from './OtelSqlView';
|
package/lib/otel/views/index.js
CHANGED
|
@@ -16,6 +16,8 @@ export { OtelSearchBar } from './OtelSearchBar.js';
|
|
|
16
16
|
export { OtelSpanDetail } from './OtelSpanDetail.js';
|
|
17
17
|
export { OtelSpanTree } from './OtelSpanTree.js';
|
|
18
18
|
export { OtelTimeline } from './OtelTimeline.js';
|
|
19
|
+
export { OtelLiveSpans, DEFAULT_SPAN_FACETS, filterSpansByFacets, } from './OtelLiveSpans.js';
|
|
20
|
+
export { unfoldedValue } from './OtelSpanDetail.js';
|
|
19
21
|
export { OtelTimelineRangeSlider } from './OtelTimelineRangeSlider.js';
|
|
20
22
|
export { OtelSqlView } from './OtelSqlView.js';
|
|
21
23
|
export { OtelSystemView } from './OtelSystemView.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What any JavaScript runtime can import from Core: no React, DOM, Jupyter or
|
|
3
|
+
* Node built-in. Re-exports only; the mobile app reaches Core through this
|
|
4
|
+
* entry alone.
|
|
5
|
+
*
|
|
6
|
+
* @module portable
|
|
7
|
+
*/
|
|
8
|
+
export { personOf, type Person } from '../models/Person';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* What any JavaScript runtime can import from Core: no React, DOM, Jupyter or
|
|
7
|
+
* Node built-in. Re-exports only; the mobile app reaches Core through this
|
|
8
|
+
* entry alone.
|
|
9
|
+
*
|
|
10
|
+
* @module portable
|
|
11
|
+
*/
|
|
12
|
+
export { personOf } from '../models/Person.js';
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* shows. `home` is the whole platform, shown as *Power*, at `/power`: the key
|
|
8
8
|
* is kept so a cookie written before the Studio existed still names the
|
|
9
9
|
* shell it meant. `agentify` is the business cases; `admin` is the
|
|
10
|
-
* platform's administration. `
|
|
11
|
-
* by drag and drop or in Python — the Studio's app pages under
|
|
12
|
-
* their own, and the
|
|
10
|
+
* platform's administration. `apps` is the applications: AI applications
|
|
11
|
+
* built by spec, by drag and drop or in Python — the Studio's app pages under
|
|
12
|
+
* a sidebar of their own, every address under `/apps`, and the workspace at
|
|
13
|
+
* `/apps` itself. It was `loop`, at `/loop`: a cookie still saying `loop`
|
|
14
|
+
* names no shell and is ignored, the reader opening on the Studio.
|
|
13
15
|
*/
|
|
14
|
-
export type NavigationView = 'studio' | 'home' | 'agentify' | 'admin' | '
|
|
16
|
+
export type NavigationView = 'studio' | 'home' | 'agentify' | 'admin' | 'apps';
|
|
15
17
|
/** The shells, and where each one opens when it has not been visited. */
|
|
16
18
|
export declare const NAVIGATION_VIEW_HOMES: Record<NavigationView, string>;
|
|
17
19
|
/** Well-known view identifiers for {@link NavigationState.tabsByView}. */
|
|
@@ -39,8 +39,8 @@ export const NAVIGATION_VIEW_HOMES = {
|
|
|
39
39
|
home: '/power',
|
|
40
40
|
agentify: '/agentify',
|
|
41
41
|
admin: '/admin',
|
|
42
|
-
// The applications: what a builder opens
|
|
43
|
-
|
|
42
|
+
// The applications: what a builder opens the Apps shell for.
|
|
43
|
+
apps: '/apps/apps',
|
|
44
44
|
};
|
|
45
45
|
/** Well-known view identifiers for {@link NavigationState.tabsByView}. */
|
|
46
46
|
export const LIBRARY_VIEW = 'library';
|
|
@@ -59,7 +59,7 @@ const isView = (value) => value === 'studio' ||
|
|
|
59
59
|
value === 'home' ||
|
|
60
60
|
value === 'agentify' ||
|
|
61
61
|
value === 'admin' ||
|
|
62
|
-
value === '
|
|
62
|
+
value === 'apps';
|
|
63
63
|
function readCookie(name) {
|
|
64
64
|
if (typeof document === 'undefined') {
|
|
65
65
|
return undefined;
|
|
@@ -247,8 +247,9 @@ export function viewForRoute(route, current) {
|
|
|
247
247
|
if (path === '/admin' || path.startsWith('/admin/')) {
|
|
248
248
|
return 'admin';
|
|
249
249
|
}
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
// The Apps shell, and the hosted applications at `/apps/<slug>` with it.
|
|
251
|
+
if (path === '/apps' || path.startsWith('/apps/')) {
|
|
252
|
+
return 'apps';
|
|
252
253
|
}
|
|
253
254
|
return 'home';
|
|
254
255
|
}
|
|
@@ -116,20 +116,11 @@ export const APIKeyEdit = ({ apiKeysListRoute = '/settings/iam/api-keys', showTi
|
|
|
116
116
|
},
|
|
117
117
|
});
|
|
118
118
|
};
|
|
119
|
-
return (_jsxs(_Fragment, { children: [showTitle && (_jsx(PageHeader, { children: _jsx(Heading, { sx: { fontSize: 3 }, children: "API Key" }) })), _jsxs(Box, { display: "flex", children: [_jsxs(Box, { children: [_jsx(BoringAvatar, { displayName: apiKey?.name, size: 100, style: { paddingRight: 10 } }), _jsx(Text, { as: "h2", sx: { paddingTop: 3 }, children: apiKey?.name }), _jsx(Box, { mt: 3, children: _jsx(Label, { size: "large", children: apiKey?.variant }) })] }), _jsxs(Box, { ml: 10, children: [_jsxs(Box, { sx: { label: { marginTop: 2 } }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: formValues.name, onChange: nameNameChange }), validationResult.name === false && (_jsx(FormControl.Validation, { variant: "error", children: "Name must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, value: formValues.description, onChange: nameDescriptionChange, rows: 5 }), validationResult.description === false && (_jsx(FormControl.Validation, { variant: "error", children: "Description must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Expiration date" }), _jsx(TextInput, { block: true, value: apiKey?.expirationDate.toLocaleDateString(), onChange: nameNameChange, disabled: true })] }), _jsx(Button, { variant: "primary", disabled: !validationResult.name || !validationResult.description, sx: { marginTop: 3 }, onClick: submitAPIKey, children: "Update API key" })] }), _jsxs(Box, {
|
|
119
|
+
return (_jsxs(_Fragment, { children: [showTitle && (_jsx(PageHeader, { children: _jsx(Heading, { sx: { fontSize: 3 }, children: "API Key" }) })), _jsxs(Box, { display: "flex", children: [_jsxs(Box, { children: [_jsx(BoringAvatar, { displayName: apiKey?.name, size: 100, style: { paddingRight: 10 } }), _jsx(Text, { as: "h2", sx: { paddingTop: 3 }, children: apiKey?.name }), _jsx(Box, { mt: 3, children: _jsx(Label, { size: "large", children: apiKey?.variant }) })] }), _jsxs(Box, { ml: 10, children: [_jsxs(Box, { sx: { label: { marginTop: 2 } }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: formValues.name, onChange: nameNameChange }), validationResult.name === false && (_jsx(FormControl.Validation, { variant: "error", children: "Name must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, value: formValues.description, onChange: nameDescriptionChange, rows: 5 }), validationResult.description === false && (_jsx(FormControl.Validation, { variant: "error", children: "Description must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Expiration date" }), _jsx(TextInput, { block: true, value: apiKey?.expirationDate.toLocaleDateString(), onChange: nameNameChange, disabled: true })] }), _jsx(Button, { variant: "primary", disabled: !validationResult.name || !validationResult.description, sx: { marginTop: 3 }, onClick: submitAPIKey, children: "Update API key" })] }), _jsxs(Box, { marginTop: 3, children: [_jsx(Heading, { as: "h2", sx: {
|
|
120
120
|
fontSize: 4,
|
|
121
121
|
fontWeight: 'normal',
|
|
122
122
|
color: 'danger.fg',
|
|
123
123
|
mb: 2,
|
|
124
|
-
}, children: "Danger zone" }), _jsxs(Box, { sx: {
|
|
125
|
-
border: '1px solid',
|
|
126
|
-
borderColor: 'danger.emphasis',
|
|
127
|
-
borderRadius: 2,
|
|
128
|
-
p: 3,
|
|
129
|
-
display: 'flex',
|
|
130
|
-
alignItems: 'center',
|
|
131
|
-
justifyContent: 'space-between',
|
|
132
|
-
gap: 3,
|
|
133
|
-
}, children: [_jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'danger.fg' }, children: "Confirm the API key name to delete" }), _jsx(FormControl, { children: _jsx(TextInput, { block: true, value: formValues.nameConfirm, onChange: nameConfirmChange }) }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "This operation is not reversible." })] }), _jsx(Button, { variant: "danger", disabled: !validationResult.nameConfirm, onClick: deleteAPIKey, children: "Delete API key" })] })] })] })] })] }));
|
|
124
|
+
}, children: "Danger zone" }), _jsxs(Box, { border: "1px solid", borderColor: "danger.emphasis", borderRadius: 2, p: 3, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 3, children: [_jsxs(Box, { display: "grid", gap: 1, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'danger.fg' }, children: "Confirm the API key name to delete" }), _jsx(FormControl, { children: _jsx(TextInput, { block: true, value: formValues.nameConfirm, onChange: nameConfirmChange }) }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "This operation is not reversible." })] }), _jsx(Button, { variant: "danger", disabled: !validationResult.nameConfirm, onClick: deleteAPIKey, children: "Delete API key" })] })] })] })] })] }));
|
|
134
125
|
};
|
|
135
126
|
export default APIKeyEdit;
|
|
@@ -95,25 +95,9 @@ export const APIKeyNew = ({ apiKeysListRoute = '/settings/iam/api-keys', showTit
|
|
|
95
95
|
},
|
|
96
96
|
});
|
|
97
97
|
};
|
|
98
|
-
return (_jsx(Box, { children: showAPIKey ? (_jsxs(_Fragment, { children: [_jsx(PageHeader, { children: _jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "Your API Key Is Created" }) }) }), _jsxs(Box, { sx: {
|
|
99
|
-
border: '1px solid',
|
|
100
|
-
borderColor: 'accent.muted',
|
|
101
|
-
borderRadius: 2,
|
|
102
|
-
p: 3,
|
|
103
|
-
bg: 'canvas.subtle',
|
|
104
|
-
mb: 3,
|
|
105
|
-
}, children: [_jsx(Text, { sx: { fontWeight: 600, color: 'accent.fg' }, children: "Important" }), _jsx(Text, { as: "p", sx: { mt: 1, color: 'fg.muted' }, children: "Save this API key now. You will not be able to see the full value again after leaving this page." })] }), _jsxs(Box, { sx: {
|
|
106
|
-
border: '1px solid',
|
|
107
|
-
borderColor: 'border.default',
|
|
108
|
-
borderRadius: 2,
|
|
109
|
-
p: 3,
|
|
110
|
-
bg: 'canvas.default',
|
|
111
|
-
}, children: [_jsxs(Box, { display: "grid", gridTemplateColumns: "minmax(120px, 180px) 1fr", sx: { rowGap: 2, columnGap: 3, mb: 3 }, children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Name" }), _jsx(Text, { sx: { fontWeight: 600 }, children: apiKey?.name || '-' }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Description" }), _jsx(Text, { children: apiKey?.description || '-' }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Expiration date" }), _jsx(Text, { children: apiKey?.expirationDate
|
|
98
|
+
return (_jsx(Box, { children: showAPIKey ? (_jsxs(_Fragment, { children: [_jsx(PageHeader, { children: _jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "Your API Key Is Created" }) }) }), _jsxs(Box, { border: "1px solid", borderColor: "accent.muted", borderRadius: 2, p: 3, bg: "canvas.subtle", mb: 3, children: [_jsx(Text, { sx: { fontWeight: 600, color: 'accent.fg' }, children: "Important" }), _jsx(Text, { as: "p", sx: { mt: 1, color: 'fg.muted' }, children: "Save this API key now. You will not be able to see the full value again after leaving this page." })] }), _jsxs(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, p: 3, bg: "canvas.default", children: [_jsxs(Box, { display: "grid", gridTemplateColumns: "minmax(120px, 180px) 1fr", rowGap: 2, columnGap: 3, mb: 3, children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Name" }), _jsx(Text, { sx: { fontWeight: 600 }, children: apiKey?.name || '-' }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Description" }), _jsx(Text, { children: apiKey?.description || '-' }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Expiration date" }), _jsx(Text, { children: apiKey?.expirationDate
|
|
112
99
|
? `${apiKey.expirationDate.toLocaleString()} (${apiKey.expirationDate.toISOString()})`
|
|
113
|
-
: '-' })] }), _jsx(Text, { sx: { color: 'fg.muted', mb: 2 }, children: "API key value" }), _jsxs(Box, { display: "flex", sx: {
|
|
114
|
-
alignItems: 'flex-start',
|
|
115
|
-
gap: 2,
|
|
116
|
-
}, children: [_jsx(Text, { as: "code", sx: {
|
|
100
|
+
: '-' })] }), _jsx(Text, { sx: { color: 'fg.muted', mb: 2 }, children: "API key value" }), _jsxs(Box, { display: "flex", alignItems: "flex-start", gap: 2, children: [_jsx(Text, { as: "code", sx: {
|
|
117
101
|
color: 'fg.default',
|
|
118
102
|
bg: 'canvas.inset',
|
|
119
103
|
border: '1px solid',
|
|
@@ -133,7 +117,7 @@ export const APIKeyNew = ({ apiKeysListRoute = '/settings/iam/api-keys', showTit
|
|
|
133
117
|
variant: 'success',
|
|
134
118
|
});
|
|
135
119
|
}
|
|
136
|
-
} })] })] }), _jsx(Box, { mt: 3, children: _jsx(Button, { onClick: e => navigate(apiKeysListRoute, e), children: "List my API Keys" }) })] })) : (_jsxs(_Fragment, { children: [showTitle ? (_jsx(PageHeader, { children: _jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "New API Key" }) }) })) : null, _jsx(Box, { display: "grid", gridTemplateColumns: "1fr 1fr",
|
|
120
|
+
} })] })] }), _jsx(Box, { mt: 3, children: _jsx(Button, { onClick: e => navigate(apiKeysListRoute, e), children: "List my API Keys" }) })] })) : (_jsxs(_Fragment, { children: [showTitle ? (_jsx(PageHeader, { children: _jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "New API Key" }) }) })) : null, _jsx(Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 3, children: _jsx(Box, { children: _jsxs(Box, { sx: { label: { marginTop: 2 } }, children: [_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Type" }), _jsxs(Select, { name: "type", value: formValues.variant, onChange: valueVariantChange, children: [_jsx(Select.Option, { value: "secret", children: "Secret" }), _jsx(Select.Option, { value: "publishable", disabled: true, children: "Publishable (coming soon)" }), _jsx(Select.Option, { value: "restricted", disabled: true, children: "Restricted (coming soon)" }), _jsx(Select.Option, { value: "temporary", disabled: true, children: "Temporary (coming soon)" })] }), _jsx(FormControl.Caption, { children: "Secret is currently available. Additional API key types are coming soon." })] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: formValues.name, onChange: valueNameChange, autoFocus: true }), _jsx(FormControl.Caption, { children: "Hint: The token name is a short name that identifies in a unique way your token." }), validationResult.name === false && (_jsx(FormControl.Validation, { variant: "error", children: "Name length must be between 2 and 32 characters." }))] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Expiration day" }), _jsx(Calendar, { showGoToToday: true, onSelectDate: expirationDateChange, value: formValues.expirationDate, strings: defaultCalendarStrings }), validationResult.expirationDate !== true ? (_jsx(FormControl.Validation, { variant: "error", children: "Pick an expiration date in the future." })) : (_jsxs(FormControl.Validation, { variant: "success", children: ["Expiration date:", ' ', formValues.expirationDate?.toLocaleDateString(), "."] }))] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, value: formValues.description, onChange: valueDescriptionChange }), validationResult.description === false && (_jsx(FormControl.Validation, { variant: "error", children: "Description must have more than 2 characters." }))] }), _jsx(Button, { variant: "primary", disabled: !validationResult.name ||
|
|
137
121
|
!validationResult.description ||
|
|
138
122
|
!validationResult.expirationDate, sx: { marginTop: 2 }, onClick: e => {
|
|
139
123
|
e.preventDefault();
|
|
@@ -20,10 +20,10 @@ const APIKeysTable = ({ apiKeysListRoute, showTitle = true, }) => {
|
|
|
20
20
|
const [deletingAPIKey, setDeletingAPIKey] = useState(null);
|
|
21
21
|
const [deleteNameConfirm, setDeleteNameConfirm] = useState('');
|
|
22
22
|
const returnFocusRef = useRef(null);
|
|
23
|
-
const showInitialSpinner = apiKeys.length === 0
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const showInitialSpinner = apiKeys.length === 0 &&
|
|
24
|
+
(getAPIKeysQuery.isLoading ||
|
|
25
|
+
getAPIKeysQuery.isFetching ||
|
|
26
|
+
!Array.isArray(getAPIKeysQuery.data));
|
|
27
27
|
useEffect(() => {
|
|
28
28
|
if (getAPIKeysQuery.data) {
|
|
29
29
|
const normalized = getAPIKeysQuery.data.filter((apiKey) => Boolean(apiKey));
|
|
@@ -85,12 +85,7 @@ const APIKeysTable = ({ apiKeysListRoute, showTitle = true, }) => {
|
|
|
85
85
|
navigate(routeOf(id));
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
|
-
return apiKeys.length === 0 ? (_jsx(Blankslate, { border: true, spacious: true, children: showInitialSpinner ? (_jsx(Box, { sx: {
|
|
89
|
-
display: 'flex',
|
|
90
|
-
alignItems: 'center',
|
|
91
|
-
justifyContent: 'center',
|
|
92
|
-
minHeight: '40px',
|
|
93
|
-
}, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [showTitle && _jsx(Blankslate.Heading, { children: "API Keys" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No API Keys found." }) })] })) })) : (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { onClick: openClickedRow, sx: {
|
|
88
|
+
return apiKeys.length === 0 ? (_jsx(Blankslate, { border: true, spacious: true, children: showInitialSpinner ? (_jsx(Box, { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "40px", children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [showTitle && _jsx(Blankslate.Heading, { children: "API Keys" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No API Keys found." }) })] })) })) : (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { onClick: openClickedRow, sx: {
|
|
94
89
|
'& tbody tr': { cursor: 'pointer' },
|
|
95
90
|
'& tbody tr:hover': { bg: 'canvas.subtle' },
|
|
96
91
|
}, children: [showTitle && (_jsxs(_Fragment, { children: [_jsx(Table.Title, { as: "h2", id: "api-keys", children: "API Keys" }), _jsx(Table.Subtitle, { as: "p", id: "api-keys-subtitle", children: "Your API keys." })] })), _jsx(DataTable, { "aria-labelledby": "api-keys", "aria-describedby": "api-keys-subtitle", data: apiKeys, columns: [
|
|
@@ -117,7 +112,7 @@ const APIKeysTable = ({ apiKeysListRoute, showTitle = true, }) => {
|
|
|
117
112
|
{
|
|
118
113
|
header: '',
|
|
119
114
|
field: 'id',
|
|
120
|
-
renderCell: apiKey => (_jsxs(Box, { display: "flex",
|
|
115
|
+
renderCell: apiKey => (_jsxs(Box, { display: "flex", gap: 1, children: [_jsx(IconButton, { icon: EditIcon, "aria-label": "Edit", size: "small", variant: "invisible", onClick: e => navigate(routeOf(apiKey.id), e) }), _jsx(IconButton, { ref: returnFocusRef, icon: TrashIcon, "aria-label": "Delete", size: "small", variant: "invisible", sx: { color: 'danger.fg' }, onClick: () => {
|
|
121
116
|
setDeletingAPIKey(apiKey);
|
|
122
117
|
setDeleteNameConfirm('');
|
|
123
118
|
} })] })),
|
|
@@ -80,7 +80,7 @@ const APIKeysTable = () => {
|
|
|
80
80
|
{
|
|
81
81
|
header: '',
|
|
82
82
|
field: 'id',
|
|
83
|
-
renderCell: apiKey => (_jsxs(Box, { display: "flex",
|
|
83
|
+
renderCell: apiKey => (_jsxs(Box, { display: "flex", gap: 1, children: [_jsx(IconButton, { icon: EditIcon, "aria-label": "Edit", size: "small", variant: "invisible", onClick: e => navigate(`${apiKey.id}`, e) }), _jsx(IconButton, { ref: returnFocusRef, icon: TrashIcon, "aria-label": "Delete", size: "small", variant: "invisible", sx: { color: 'danger.fg' }, onClick: () => {
|
|
84
84
|
setDeletingAPIKey(apiKey);
|
|
85
85
|
setDeleteNameConfirm('');
|
|
86
86
|
} })] })),
|
|
@@ -11,5 +11,5 @@ export type DatasourceDetailProps = {
|
|
|
11
11
|
* two people editing the same record do not overwrite each other. Running
|
|
12
12
|
* queries is the Contents detail's job, not this page's.
|
|
13
13
|
*/
|
|
14
|
-
export declare const DatasourceDetail: ({ sourceUid: givenUid }?: DatasourceDetailProps) => import("react").JSX.Element;
|
|
14
|
+
export declare const DatasourceDetail: ({ sourceUid: givenUid, }?: DatasourceDetailProps) => import("react").JSX.Element;
|
|
15
15
|
export default DatasourceDetail;
|
|
@@ -14,10 +14,10 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
14
14
|
import { DATASOURCE_CONNECTOR_LABELS, } from '../../api/contents/index.js';
|
|
15
15
|
import { BoringAvatar } from '../../components/avatars/index.js';
|
|
16
16
|
import { useContentSource, useCredentialDiagnostics, useTestDatasource, useToast, useUpdateContentSource, } from '../../hooks/index.js';
|
|
17
|
-
function Row({ label, children }) {
|
|
18
|
-
return (_jsxs(Box, {
|
|
17
|
+
function Row({ label, children, }) {
|
|
18
|
+
return (_jsxs(Box, { display: "grid", gridTemplateColumns: ['1fr', '200px 1fr'], gap: 1, children: [_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: label }), _jsx(Box, { fontSize: 1, children: children })] }));
|
|
19
19
|
}
|
|
20
|
-
const frame = (children) => (_jsx(PageLayout, { containerWidth: "full", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: _jsx(PageLayout.Content, { children: _jsx(Box, {
|
|
20
|
+
const frame = (children) => (_jsx(PageLayout, { containerWidth: "full", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: _jsx(PageLayout.Content, { children: _jsx(Box, { maxWidth: 960, mx: "auto", width: "100%", children: children }) }) }));
|
|
21
21
|
/**
|
|
22
22
|
* A Datasource's settings: what it connects to, and the name it goes by.
|
|
23
23
|
*
|
|
@@ -27,7 +27,7 @@ const frame = (children) => (_jsx(PageLayout, { containerWidth: "full", padding:
|
|
|
27
27
|
* two people editing the same record do not overwrite each other. Running
|
|
28
28
|
* queries is the Contents detail's job, not this page's.
|
|
29
29
|
*/
|
|
30
|
-
export const DatasourceDetail = ({ sourceUid: givenUid } = {}) => {
|
|
30
|
+
export const DatasourceDetail = ({ sourceUid: givenUid, } = {}) => {
|
|
31
31
|
const params = useParams();
|
|
32
32
|
const sourceUid = givenUid ?? params.sourceUid ?? params.datasourceId;
|
|
33
33
|
const { enqueueToast } = useToast();
|
|
@@ -45,7 +45,9 @@ export const DatasourceDetail = ({ sourceUid: givenUid } = {}) => {
|
|
|
45
45
|
if (source && configuration) {
|
|
46
46
|
setName(source.name);
|
|
47
47
|
setDescription(source.description ?? '');
|
|
48
|
-
setRowLimit(configuration.defaultRowLimit
|
|
48
|
+
setRowLimit(configuration.defaultRowLimit
|
|
49
|
+
? String(configuration.defaultRowLimit)
|
|
50
|
+
: '');
|
|
49
51
|
setMaxSeconds(configuration.maxSeconds ? String(configuration.maxSeconds) : '');
|
|
50
52
|
}
|
|
51
53
|
}, [source, configuration]);
|
|
@@ -53,10 +55,13 @@ export const DatasourceDetail = ({ sourceUid: givenUid } = {}) => {
|
|
|
53
55
|
return _jsx(_Fragment, {});
|
|
54
56
|
}
|
|
55
57
|
if (sourceQuery.isPending) {
|
|
56
|
-
return frame(_jsx(Box, {
|
|
58
|
+
return frame(_jsx(Box, { display: "flex", justifyContent: "center", alignItems: "center", py: 6, children: _jsx(Spinner, { size: "large" }) }));
|
|
57
59
|
}
|
|
58
|
-
if (sourceQuery.isError ||
|
|
59
|
-
|
|
60
|
+
if (sourceQuery.isError ||
|
|
61
|
+
!source ||
|
|
62
|
+
!configuration ||
|
|
63
|
+
source.kind !== 'datasource') {
|
|
64
|
+
return frame(_jsx(Box, { p: 4, children: _jsx(Text, { sx: { color: 'danger.fg' }, children: sourceQuery.isError
|
|
60
65
|
? sourceQuery.error.message
|
|
61
66
|
: 'Datasource not found, or this content is not a Datasource.' }) }));
|
|
62
67
|
}
|
|
@@ -86,14 +91,16 @@ export const DatasourceDetail = ({ sourceUid: givenUid } = {}) => {
|
|
|
86
91
|
onSuccess: () => enqueueToast('The Datasource is updated.', { variant: 'success' }),
|
|
87
92
|
});
|
|
88
93
|
};
|
|
89
|
-
const connector = DATASOURCE_CONNECTOR_LABELS[configuration.connectorType]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
const connector = DATASOURCE_CONNECTOR_LABELS[configuration.connectorType] ?? configuration.connectorType;
|
|
95
|
+
return frame(_jsxs(_Fragment, { children: [_jsxs(Box, { mb: 4, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Datasource" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "The connection as Contents holds it. The credential is never shown; the page says whether one is attached and resolvable." })] }), _jsx(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.default", p: 3, children: _jsxs(Box, { display: "grid", gridTemplateColumns: ['1fr', '180px 1fr'], gap: 4, children: [_jsxs(Box, { children: [_jsx(BoringAvatar, { displayName: source.name, size: 100, style: { paddingRight: 10 } }), _jsx(Box, { mt: 3, children: _jsx(Label, { size: "large", children: connector }) })] }), _jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Row, { label: "Connector", children: _jsx(Text, { children: connector }) }), _jsxs(Row, { label: "Route", children: [_jsx(Label, { variant: configuration.networkRoute === 'dataserver'
|
|
96
|
+
? 'accent'
|
|
97
|
+
: 'secondary', children: configuration.networkRoute === 'dataserver'
|
|
98
|
+
? 'Through a Dataserver'
|
|
99
|
+
: 'Direct' }), configuration.dataServerUid && (_jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, ml: 2 }, children: configuration.dataServerUid }))] }), configuration.endpoint && (_jsx(Row, { label: "Endpoint", children: _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: configuration.endpoint }) })), configuration.databaseOrProject && (_jsx(Row, { label: configuration.connectorType === 'bigquery'
|
|
100
|
+
? 'Project'
|
|
101
|
+
: 'Database', children: _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: configuration.databaseOrProject }) })), _jsx(Row, { label: "Allowed operations", children: _jsx(Text, { children: (configuration.allowedOperations ?? []).join(', ') || '—' }) }), _jsx(Row, { label: "Credential", children: configuration.networkRoute === 'dataserver' ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Held by the Dataserver, in your network." })) : configuration.credentialUid ? (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [diagnostics.data ? (_jsx(Label, { variant: diagnostics.data.resolvable ? 'success' : 'danger', children: diagnostics.data.resolvable
|
|
102
|
+
? 'Resolvable'
|
|
103
|
+
: 'Not resolvable' })) : (_jsx(Label, { variant: "success", children: "Attached" })), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: diagnostics.data?.credentialName ??
|
|
104
|
+
'Held in Vault and resolved server-side.' })] })) : (_jsx(Label, { variant: "attention", children: "None" })) }), _jsx(Row, { label: "Connection", children: _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Button, { size: "small", disabled: test.isPending || !permissions.execute, onClick: () => test.mutate(source.uid), children: test.isPending ? 'Testing…' : 'Test connection' }), test.data && (_jsxs(_Fragment, { children: [_jsx(Label, { variant: test.data.ok ? 'success' : 'danger', children: test.data.ok ? 'Reachable' : 'Not reachable' }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: test.data.detail })] })), test.isError && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 0 }, children: test.error.message }))] }) })] }), _jsxs(Box, { sx: { label: { marginTop: 2 } }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: name, disabled: !permissions.update, onChange: event => setName(event.target.value) }), !nameValid && (_jsx(FormControl.Validation, { variant: "error", children: "Name must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, rows: 3, value: description, disabled: !permissions.update, onChange: event => setDescription(event.target.value) })] }), _jsxs(Box, { display: "grid", gridTemplateColumns: ['1fr', '1fr 1fr'], gap: 3, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Default row limit" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: rowLimit, placeholder: "Service default", disabled: !permissions.update, onChange: event => setRowLimit(event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Max seconds" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: maxSeconds, placeholder: "Service default", disabled: !permissions.update, onChange: event => setMaxSeconds(event.target.value) })] })] }), update.isError && (_jsx(Text, { as: "p", sx: { color: 'danger.fg' }, children: update.error.message })), permissions.update && (_jsx(Box, { marginTop: 3, children: _jsx(Button, { variant: "primary", disabled: !nameValid || update.isPending, onClick: save, children: update.isPending ? 'Saving…' : 'Update Datasource' }) }))] })] })] }) })] }));
|
|
98
105
|
};
|
|
99
106
|
export default DatasourceDetail;
|
|
@@ -128,15 +128,11 @@ export const DatasourceNew = ({ datasourcesListRoute = '/datasources', secretsRo
|
|
|
128
128
|
// The error is shown under the form; the idempotency key makes a retry safe.
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
|
-
return (_jsx(PageLayout, { containerWidth: "full", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: _jsx(PageLayout.Content, { children: _jsxs(Box, {
|
|
131
|
+
return (_jsx(PageLayout, { containerWidth: "full", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: _jsx(PageLayout.Content, { children: _jsxs(Box, { maxWidth: 960, mx: "auto", width: "100%", children: [_jsxs(Box, { mb: 4, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Connect a Datasource" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "A database, warehouse or query service your notebooks and agents query through Datalayer. The credential stays in Vault; a query receives a short-lived, scoped connection." }), accountPrincipal && _jsx(Box, { mt: 2, children: accountPrincipal })] }), _jsxs(Box, { as: "form", onSubmit: submit, display: "grid", gap: 3, children: [_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Connector" }), _jsx(Select, { value: connectorType, onChange: event => setConnectorType(event.target.value), children: CONNECTORS.map(connector => (_jsx(Select.Option, { value: connector, children: DATASOURCE_CONNECTOR_LABELS[connector] }, connector))) }), _jsx(FormControl.Caption, { children: labels.hint })] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: name, onChange: event => setName(event.target.value), autoFocus: true }), name.length > 0 && !nameValid && (_jsx(FormControl.Validation, { variant: "error", children: "Name must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, value: description, onChange: event => setDescription(event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: labels.endpoint }), _jsx(TextInput, { block: true, monospace: true, value: endpoint, placeholder: connectorType === 'sql' ? 'warehouse.internal:5432' : '', onChange: event => setEndpoint(event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: labels.target }), _jsx(TextInput, { block: true, monospace: true, value: target, onChange: event => setTarget(event.target.value) })] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Network route" }), _jsxs(Select, { value: route, onChange: event => setRoute(event.target.value), children: [_jsx(Select.Option, { value: "direct", children: "Direct \u2014 Datalayer reaches the endpoint" }), _jsx(Select.Option, { value: "dataserver", children: "Through a Dataserver in your network" })] }), _jsx(FormControl.Caption, { children: "A private endpoint is reached through a Dataserver, which holds the credential in the network the database lives in." })] }), route === 'direct' ? (_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Credential" }), _jsxs(Select, { value: credentialUid, onChange: event => setCredentialUid(event.target.value), children: [_jsx(Select.Option, { value: "", children: "Choose a Secret\u2026" }), (secrets.data ?? []).map(secret => (_jsx(Select.Option, { value: secret.id, children: secret.name }, secret.id)))] }), _jsxs(FormControl.Caption, { children: ["Held in Vault and resolved server-side for each query; never handed to notebook code. Add one under", ' ', _jsx(Link, { href: secretsRoute, onClick: event => {
|
|
132
132
|
event.preventDefault();
|
|
133
133
|
navigate(secretsRoute, event);
|
|
134
134
|
}, children: "Secrets" }), "."] })] })) : (_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Dataserver" }), _jsxs(Select, { value: dataServerUid, onChange: event => setDataServerUid(event.target.value), children: [_jsx(Select.Option, { value: "", children: "Choose a Dataserver\u2026" }), dataserverOptions.map(option => (_jsxs(Select.Option, { value: option.uid, children: [option.name, " (", option.state, ")"] }, option.uid)))] }), _jsx(FormControl.Caption, { children: dataserverOptions.length === 0
|
|
135
135
|
? 'No Dataserver is registered yet; register one under Dataservers first.'
|
|
136
|
-
: 'The gateway that reaches the endpoint. Contents routes to it only while it is online.' })] })), _jsxs(CheckboxGroup, { children: [_jsx(CheckboxGroup.Label, { children: "Allowed operations" }), DATASOURCE_OPERATIONS.map(operation => (_jsxs(FormControl, { children: [_jsx(Checkbox, { value: operation, checked: operations.includes(operation), onChange: event => toggleOperation(operation, event.target.checked) }), _jsx(FormControl.Label, { sx: { textTransform: 'capitalize' }, children: operation })] }, operation))), _jsx(CheckboxGroup.Caption, { children: "The service refuses any statement outside this list before a query exists. Writes are never allowed through a Datasource." })] }), _jsxs(Box, {
|
|
137
|
-
display: 'grid',
|
|
138
|
-
gridTemplateColumns: ['1fr', 'repeat(3, minmax(0, 1fr))'],
|
|
139
|
-
gap: 3,
|
|
140
|
-
}, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Default row limit" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: rowLimit, onChange: event => setRowLimit(event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Max bytes" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: maxBytes, placeholder: "Unlimited", onChange: event => setMaxBytes(event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Max seconds" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: maxSeconds, onChange: event => setMaxSeconds(event.target.value) })] })] }), createSource.isError && (_jsx(Flash, { variant: "danger", children: createSource.error.message })), _jsxs(Box, { sx: { display: 'flex', gap: 2 }, children: [_jsx(Button, { type: "button", onClick: event => navigate(datasourcesListRoute, event), children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !canSubmit, children: createSource.isPending ? 'Connecting…' : 'Connect Datasource' })] })] })] }) }) }));
|
|
136
|
+
: 'The gateway that reaches the endpoint. Contents routes to it only while it is online.' })] })), _jsxs(CheckboxGroup, { children: [_jsx(CheckboxGroup.Label, { children: "Allowed operations" }), DATASOURCE_OPERATIONS.map(operation => (_jsxs(FormControl, { children: [_jsx(Checkbox, { value: operation, checked: operations.includes(operation), onChange: event => toggleOperation(operation, event.target.checked) }), _jsx(FormControl.Label, { sx: { textTransform: 'capitalize' }, children: operation })] }, operation))), _jsx(CheckboxGroup.Caption, { children: "The service refuses any statement outside this list before a query exists. Writes are never allowed through a Datasource." })] }), _jsxs(Box, { display: "grid", gridTemplateColumns: ['1fr', 'repeat(3, minmax(0, 1fr))'], gap: 3, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Default row limit" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: rowLimit, onChange: event => setRowLimit(event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Max bytes" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: maxBytes, placeholder: "Unlimited", onChange: event => setMaxBytes(event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Max seconds" }), _jsx(TextInput, { block: true, type: "number", min: 1, value: maxSeconds, onChange: event => setMaxSeconds(event.target.value) })] })] }), createSource.isError && (_jsx(Flash, { variant: "danger", children: createSource.error.message })), _jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Button, { type: "button", onClick: event => navigate(datasourcesListRoute, event), children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !canSubmit, children: createSource.isPending ? 'Connecting…' : 'Connect Datasource' })] })] })] }) }) }));
|
|
141
137
|
};
|
|
142
138
|
export default DatasourceNew;
|