@datalayer/core 1.2.18 → 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/views/OtelLive.js +4 -62
- package/lib/otel/views/OtelLiveSpans.js +4 -29
- 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.js +9 -79
- 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 +12 -76
- 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
|
@@ -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;
|
|
@@ -88,12 +88,7 @@ const LiveDatasourcesTable = ({ datasourcesListRoute, spaceUid, showInlineLoadin
|
|
|
88
88
|
if (datasourcesQuery.isError && datasources.length === 0) {
|
|
89
89
|
return (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(DatabaseIcon, { size: 24 }) }), _jsx(Blankslate.Heading, { children: "Datasources could not be listed" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: datasourcesQuery.error.message }) }), _jsx(Blankslate.PrimaryAction, { onClick: () => datasourcesQuery.refetch(), children: "Try again" })] }));
|
|
90
90
|
}
|
|
91
|
-
return showInitialSpinner ? (_jsx(Blankslate, { border: true, spacious: true, children: showInlineLoadingIndicator ? (_jsx(Box, { sx: {
|
|
92
|
-
display: 'flex',
|
|
93
|
-
alignItems: 'center',
|
|
94
|
-
justifyContent: 'center',
|
|
95
|
-
minHeight: '40px',
|
|
96
|
-
}, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsx(Blankslate.Heading, { children: "Datasources" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Loading datasources..." }) })] })) })) : (_jsx(DatasourcesTable, { datasources: datasources, datasourcesListRoute: datasourcesListRoute }));
|
|
91
|
+
return showInitialSpinner ? (_jsx(Blankslate, { border: true, spacious: true, children: showInlineLoadingIndicator ? (_jsx(Box, { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "40px", children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsx(Blankslate.Heading, { children: "Datasources" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Loading datasources..." }) })] })) })) : (_jsx(DatasourcesTable, { datasources: datasources, datasourcesListRoute: datasourcesListRoute }));
|
|
97
92
|
};
|
|
98
93
|
/**
|
|
99
94
|
* The Datasources of the Contents catalog.
|
|
@@ -104,14 +99,7 @@ const LiveDatasourcesTable = ({ datasourcesListRoute, spaceUid, showInlineLoadin
|
|
|
104
99
|
*/
|
|
105
100
|
export const Datasources = ({ newDatasourceRoute = '/datasources/new', datasourcesListRoute, spaceUid, showInlineLoadingIndicator = true, mock = false, embedded = false, } = {}) => {
|
|
106
101
|
const navigate = useNavigate();
|
|
107
|
-
const body = (_jsxs(Box, {
|
|
108
|
-
display: 'flex',
|
|
109
|
-
alignItems: 'flex-start',
|
|
110
|
-
justifyContent: 'space-between',
|
|
111
|
-
gap: 3,
|
|
112
|
-
flexWrap: 'wrap',
|
|
113
|
-
mb: embedded ? 2 : 4,
|
|
114
|
-
}, children: [_jsx(Box, { children: !embedded && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Datasources" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Databases, warehouses and query services your notebooks and agents query through Datalayer, without holding the credential." })] })) }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: DatabaseIcon, disabled: mock, onClick: e => navigate(newDatasourceRoute, e), children: "Connect Datasource" })] }), mock ? (_jsx(DatasourcesTable, { datasources: DATASOURCES_MOCK.map(toDatasourceRow), mock: true })) : (_jsx(LiveDatasourcesTable, { datasourcesListRoute: datasourcesListRoute, spaceUid: spaceUid, showInlineLoadingIndicator: showInlineLoadingIndicator }))] }));
|
|
102
|
+
const body = (_jsxs(Box, { maxWidth: embedded ? undefined : 960, mx: "auto", width: "100%", children: [_jsxs(Box, { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 3, flexWrap: "wrap", mb: embedded ? 2 : 4, children: [_jsx(Box, { children: !embedded && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Datasources" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Databases, warehouses and query services your notebooks and agents query through Datalayer, without holding the credential." })] })) }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: DatabaseIcon, disabled: mock, onClick: e => navigate(newDatasourceRoute, e), children: "Connect Datasource" })] }), mock ? (_jsx(DatasourcesTable, { datasources: DATASOURCES_MOCK.map(toDatasourceRow), mock: true })) : (_jsx(LiveDatasourcesTable, { datasourcesListRoute: datasourcesListRoute, spaceUid: spaceUid, showInlineLoadingIndicator: showInlineLoadingIndicator }))] }));
|
|
115
103
|
if (embedded) {
|
|
116
104
|
return body;
|
|
117
105
|
}
|
|
@@ -15,7 +15,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
import { useState, useCallback, useRef, useMemo, useEffect, } from 'react';
|
|
16
16
|
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
17
17
|
import { createGlobalStyle } from 'styled-components';
|
|
18
|
-
import { Box
|
|
18
|
+
import { Box } from '@datalayer/primer-addons';
|
|
19
|
+
import { Button, FormControl, Heading, Link, Spinner, Text, Textarea, TextInput, } from '@primer/react';
|
|
19
20
|
import { EyeIcon, EyeClosedIcon, KeyIcon, MailIcon, SignInIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
20
21
|
import { GithubMarkIcon, GoogleIcon, LinkedInGreyIcon, } from '@datalayer/icons-react';
|
|
21
22
|
import { useToast } from '../../hooks/index.js';
|
|
@@ -318,30 +319,12 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
|
|
|
318
319
|
}
|
|
319
320
|
}, [mfaUserUid, submit, submitMfa]);
|
|
320
321
|
const socialDisabled = socialLoading || loading || asDoc;
|
|
321
|
-
return (_jsxs(Box, { className: "signin-input-theme-scope", sx: {
|
|
322
|
-
display: 'flex',
|
|
323
|
-
alignItems: hideHero || compactDocMode || !fillHeight ? 'flex-start' : 'center',
|
|
324
|
-
justifyContent: 'center',
|
|
325
|
-
minHeight: hideHero || compactDocMode || !fillHeight ? 'auto' : '100vh',
|
|
326
|
-
bg: hideHero ? 'transparent' : 'canvas.default',
|
|
327
|
-
color: 'fg.default',
|
|
328
|
-
py: hideHero ? 0 : compactDocMode ? 2 : 4,
|
|
329
|
-
}, children: [_jsx(SignInInputGlobalStyle, {}), _jsxs(Box, { sx: {
|
|
330
|
-
width: '100%',
|
|
331
|
-
maxWidth: 440,
|
|
332
|
-
p: 0,
|
|
333
|
-
}, children: [!hideHero && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
334
|
-
display: 'flex',
|
|
335
|
-
alignItems: 'center',
|
|
336
|
-
gap: 2,
|
|
337
|
-
mb: compactDocMode ? 2 : 3,
|
|
338
|
-
justifyContent: 'center',
|
|
339
|
-
}, children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: {
|
|
322
|
+
return (_jsxs(Box, { className: "signin-input-theme-scope", display: "flex", alignItems: hideHero || compactDocMode || !fillHeight ? 'flex-start' : 'center', justifyContent: "center", minHeight: hideHero || compactDocMode || !fillHeight ? 'auto' : '100vh', bg: hideHero ? 'transparent' : 'canvas.default', color: "fg.default", py: hideHero ? 0 : compactDocMode ? 2 : 4, children: [_jsx(SignInInputGlobalStyle, {}), _jsxs(Box, { width: "100%", maxWidth: 440, p: 0, children: [!hideHero && (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, mb: compactDocMode ? 2 : 3, justifyContent: "center", children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: {
|
|
340
323
|
fontSize: 1,
|
|
341
324
|
color: 'fg.muted',
|
|
342
325
|
mb: compactDocMode ? 2 : 3,
|
|
343
326
|
textAlign: 'center',
|
|
344
|
-
}, children: description })] })), (calloutTitle || calloutDescription) && (_jsxs(Box, {
|
|
327
|
+
}, children: description })] })), (calloutTitle || calloutDescription) && (_jsxs(Box, { mb: 3, textAlign: "center", children: [calloutTitle && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: calloutTitle })), calloutDescription && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: calloutDescription }))] })), !mfaUserUid && (github || google || linkedin) && (_jsxs(_Fragment, { children: [github && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'github'
|
|
345
328
|
? () => _jsx(Spinner, { size: "small" })
|
|
346
329
|
: GithubMarkIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.github), sx: {
|
|
347
330
|
mt: socialButtonMarginTop,
|
|
@@ -359,12 +342,7 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
|
|
|
359
342
|
mt: socialButtonMarginTop,
|
|
360
343
|
maxWidth: actionButtonMaxWidth,
|
|
361
344
|
mx: 'auto',
|
|
362
|
-
}, children: "Sign in with LinkedIn" }))] })), !mfaUserUid && !showPasswordForm && (_jsx(Box, {
|
|
363
|
-
display: 'flex',
|
|
364
|
-
alignItems: 'center',
|
|
365
|
-
justifyContent: 'space-between',
|
|
366
|
-
mt: 3,
|
|
367
|
-
}, children: [_jsx(Button, { variant: "primary", disabled: loading || asDoc || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign In' }), showForgotPassword && (_jsx(Link, { as: "button", sx: {
|
|
345
|
+
}, children: "Sign in with LinkedIn" }))] })), !mfaUserUid && !showPasswordForm && (_jsx(Box, { mt: 3, display: "flex", justifyContent: "center", children: _jsx(Button, { leadingVisual: KeyIcon, size: "large", block: true, disabled: asDoc || loading || socialLoading, onClick: () => setShowPasswordForm(true), sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: passwordToggleLabel }) })), !mfaUserUid && showPasswordForm && (_jsxs(Box, { mt: 4, children: [_jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Username" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your username", value: handle, disabled: loading || asDoc, 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, disabled: loading || asDoc, onChange: e => setPassword(e.target.value), onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => setShowPassword(!showPassword), disabled: loading || asDoc, icon: showPassword ? EyeClosedIcon : EyeIcon, "aria-label": showPassword ? 'Hide password' : 'Show password', sx: { color: 'var(--fgColor-muted)' } }) })] }), _jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", mt: 3, children: [_jsx(Button, { variant: "primary", disabled: loading || asDoc || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign In' }), showForgotPassword && (_jsx(Link, { as: "button", sx: {
|
|
368
346
|
fontSize: 1,
|
|
369
347
|
cursor: asDoc ? 'not-allowed' : 'pointer',
|
|
370
348
|
background: 'none',
|
|
@@ -375,47 +353,13 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
|
|
|
375
353
|
return;
|
|
376
354
|
}
|
|
377
355
|
handleForgotPassword();
|
|
378
|
-
}, children: forgotPasswordLabel }))] })] })), mfaUserUid && (_jsxs(_Fragment, { children: [_jsxs(FormControl, { required: true, sx: { mb: 3, mt: 2 }, children: [_jsx(FormControl.Label, { children: "MFA Code" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your MFA code", value: mfaCode, disabled: loading || asDoc, onChange: e => setMfaCode(e.target.value), onKeyDown: handleKeyDown })] }), _jsx(Button, { variant: "primary", block: true, disabled: loading || asDoc || !mfaCode.trim(), onClick: submitMfa, sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: loading ? 'Validating…' : 'Validate MFA Code' })] })), error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 1, mt: 3, display: 'block' }, children: error })), signUp && (_jsxs(Box, { sx: {
|
|
379
|
-
mt: 4,
|
|
380
|
-
pt: 4,
|
|
381
|
-
textAlign: 'center',
|
|
382
|
-
borderTop: '1px solid',
|
|
383
|
-
borderColor: 'border.muted',
|
|
384
|
-
}, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: signUpTitle }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mb: 3 }, children: signUpDescription }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'center' }, children: _jsx(Button, { variant: "primary", size: "large", leadingVisual: MailIcon, disabled: asDoc || loading, onClick: handleSignUp, children: signUpLabel }) })] })), apiKey && onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
385
|
-
display: 'flex',
|
|
386
|
-
alignItems: 'center',
|
|
387
|
-
gap: 2,
|
|
388
|
-
my: 3,
|
|
389
|
-
}, children: [_jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "or" }), _jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } })] }), _jsx(Button, { block: true, leadingVisual: KeyIcon, disabled: asDoc || loading || socialLoading, onClick: () => setShowApiKeyDialog(true), sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: "Sign in with an API Key" }), showApiKeyDialog && (_jsx(Box, { role: "dialog", "aria-modal": "true", "aria-labelledby": "signin-api-key-title", onKeyDown: event => {
|
|
356
|
+
}, children: forgotPasswordLabel }))] })] })), mfaUserUid && (_jsxs(_Fragment, { children: [_jsxs(FormControl, { required: true, sx: { mb: 3, mt: 2 }, children: [_jsx(FormControl.Label, { children: "MFA Code" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your MFA code", value: mfaCode, disabled: loading || asDoc, onChange: e => setMfaCode(e.target.value), onKeyDown: handleKeyDown })] }), _jsx(Button, { variant: "primary", block: true, disabled: loading || asDoc || !mfaCode.trim(), onClick: submitMfa, sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: loading ? 'Validating…' : 'Validate MFA Code' })] })), error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 1, mt: 3, display: 'block' }, children: error })), signUp && (_jsxs(Box, { mt: 4, pt: 4, textAlign: "center", borderTop: "1px solid", borderColor: "border.muted", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: signUpTitle }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mb: 3 }, children: signUpDescription }), _jsx(Box, { display: "flex", justifyContent: "center", children: _jsx(Button, { variant: "primary", size: "large", leadingVisual: MailIcon, disabled: asDoc || loading, onClick: handleSignUp, children: signUpLabel }) })] })), apiKey && onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, my: 3, children: [_jsx(Box, { flex: 1, height: "1px", bg: "border.default" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "or" }), _jsx(Box, { flex: 1, height: "1px", bg: "border.default" })] }), _jsx(Button, { block: true, leadingVisual: KeyIcon, disabled: asDoc || loading || socialLoading, onClick: () => setShowApiKeyDialog(true), sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: "Sign in with an API Key" }), showApiKeyDialog && (_jsx(Box, { role: "dialog", "aria-modal": "true", "aria-labelledby": "signin-api-key-title", onKeyDown: event => {
|
|
390
357
|
if (event.key === 'Escape') {
|
|
391
358
|
closeApiKeyDialog();
|
|
392
359
|
}
|
|
393
|
-
}, onClick: closeApiKeyDialog,
|
|
394
|
-
position: 'fixed',
|
|
395
|
-
inset: 0,
|
|
396
|
-
zIndex: 200,
|
|
397
|
-
p: 3,
|
|
398
|
-
bg: 'canvas.backdrop',
|
|
399
|
-
display: 'flex',
|
|
400
|
-
alignItems: 'center',
|
|
401
|
-
justifyContent: 'center',
|
|
402
|
-
}, children: _jsxs(Box, { as: "form", onClick: event => event.stopPropagation(), onSubmit: event => {
|
|
360
|
+
}, onClick: closeApiKeyDialog, position: "fixed", inset: 0, zIndex: 200, p: 3, bg: "canvas.backdrop", display: "flex", alignItems: "center", justifyContent: "center", children: _jsxs(Box, { as: "form", onClick: event => event.stopPropagation(), onSubmit: event => {
|
|
403
361
|
event.preventDefault();
|
|
404
362
|
handleApiKeyAuthenticate();
|
|
405
|
-
}, sx: {
|
|
406
|
-
width: 'min(560px, 100%)',
|
|
407
|
-
p: 3,
|
|
408
|
-
borderRadius: 2,
|
|
409
|
-
border: '1px solid',
|
|
410
|
-
borderColor: 'border.default',
|
|
411
|
-
bg: 'canvas.overlay',
|
|
412
|
-
color: 'fg.default',
|
|
413
|
-
boxShadow: 'shadow.large',
|
|
414
|
-
}, children: [_jsx(Heading, { id: "signin-api-key-title", sx: { fontSize: 2, mb: 2 }, children: "Enter your API Key" }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "API Key" }), _jsx(Textarea, { block: true, required: true, autoFocus: true, placeholder: "Paste your API key here", value: apiKeyValue, onInput: e => setApiKeyValue(e.currentTarget.value), ref: apiKeyRef })] }), _jsxs(Box, { sx: {
|
|
415
|
-
mt: 3,
|
|
416
|
-
display: 'flex',
|
|
417
|
-
justifyContent: 'flex-end',
|
|
418
|
-
gap: 2,
|
|
419
|
-
}, children: [_jsx(Button, { type: "button", onClick: closeApiKeyDialog, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !apiKeyValue.trim(), children: "Authenticate" })] })] }) }))] }))] })] }));
|
|
363
|
+
}, width: "min(560px, 100%)", p: 3, borderRadius: 2, border: "1px solid", borderColor: "border.default", bg: "canvas.overlay", color: "fg.default", boxShadow: "shadow.large", children: [_jsx(Heading, { id: "signin-api-key-title", sx: { fontSize: 2, mb: 2 }, children: "Enter your API Key" }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "API Key" }), _jsx(Textarea, { block: true, required: true, autoFocus: true, placeholder: "Paste your API key here", value: apiKeyValue, onInput: e => setApiKeyValue(e.currentTarget.value), ref: apiKeyRef })] }), _jsxs(Box, { mt: 3, display: "flex", justifyContent: "flex-end", gap: 2, children: [_jsx(Button, { type: "button", onClick: closeApiKeyDialog, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !apiKeyValue.trim(), children: "Authenticate" })] })] }) }))] }))] })] }));
|
|
420
364
|
};
|
|
421
365
|
export default SignInSimple;
|
|
@@ -8,20 +8,7 @@ const Row = ({ client }) => {
|
|
|
8
8
|
const unusable = client.kind === 'unusable';
|
|
9
9
|
const icon = unusable ? (_jsx(QuestionIcon, {})) : unreadable ? (_jsx(AlertIcon, {})) : client.kind === 'hostname' ? (_jsx(GlobeIcon, {})) : (_jsx(CheckCircleIcon, {}));
|
|
10
10
|
const tone = unusable || unreadable ? 'danger.fg' : 'fg.muted';
|
|
11
|
-
return (_jsxs(Box, { sx: {
|
|
12
|
-
display: 'grid',
|
|
13
|
-
gridTemplateColumns: 'auto 1fr',
|
|
14
|
-
gap: 2,
|
|
15
|
-
alignItems: 'start',
|
|
16
|
-
py: 2,
|
|
17
|
-
borderTop: '1px solid',
|
|
18
|
-
borderColor: 'border.muted',
|
|
19
|
-
}, children: [_jsx(Box, { sx: { color: tone, pt: '2px' }, children: icon }), _jsxs(Box, { sx: { display: 'grid', gap: 1, minWidth: 0 }, children: [_jsxs(Box, { sx: {
|
|
20
|
-
display: 'flex',
|
|
21
|
-
gap: 2,
|
|
22
|
-
alignItems: 'center',
|
|
23
|
-
flexWrap: 'wrap',
|
|
24
|
-
}, children: [client.clientName && (_jsx(Text, { sx: { fontWeight: 'semibold' }, children: client.clientName })), client.kind === 'hostname' && (_jsx(Label, { variant: "accent", size: "small", children: "Whole host" })), client.localhostOnly && (
|
|
11
|
+
return (_jsxs(Box, { display: "grid", gridTemplateColumns: "auto 1fr", gap: 2, alignItems: "start", py: 2, borderTop: "1px solid", borderColor: "border.muted", children: [_jsx(Box, { color: tone, pt: "2px", children: icon }), _jsxs(Box, { display: "grid", gap: 1, minWidth: 0, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [client.clientName && (_jsx(Text, { sx: { fontWeight: 'semibold' }, children: client.clientName })), client.kind === 'hostname' && (_jsx(Label, { variant: "accent", size: "small", children: "Whole host" })), client.localhostOnly && (
|
|
25
12
|
// Worth a label of its own: every redirect goes to the reader's
|
|
26
13
|
// own machine, and a document cannot prove which local program
|
|
27
14
|
// is listening on the port.
|
|
@@ -35,7 +22,7 @@ const Row = ({ client }) => {
|
|
|
35
22
|
export const AdmittedClients = ({ scope, subjectUid, edited = false, }) => {
|
|
36
23
|
const described = useAdmittedClients(scope, subjectUid);
|
|
37
24
|
if (described.isPending && !described.data) {
|
|
38
|
-
return (_jsx(Box, {
|
|
25
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", py: 3, children: _jsx(Spinner, { size: "small" }) }));
|
|
39
26
|
}
|
|
40
27
|
// An error here is not worth a blankslate: the field above still works and
|
|
41
28
|
// the policy is unaffected. Saying nothing would be worse than saying it
|
|
@@ -54,6 +41,6 @@ export const AdmittedClients = ({ scope, subjectUid, edited = false, }) => {
|
|
|
54
41
|
return (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: answer.detail ??
|
|
55
42
|
'This layer names no client allowlist, so every client is admitted by it.' }));
|
|
56
43
|
}
|
|
57
|
-
return (_jsxs(Box, {
|
|
44
|
+
return (_jsxs(Box, { display: "grid", gap: 1, children: [edited && (_jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'attention.fg', m: 0 }, children: ["Describing the ", _jsx("strong", { children: "saved" }), " list. Save to check what you have just typed."] })), answer.clients.map(client => (_jsx(Row, { client: client }, client.entry)))] }));
|
|
58
45
|
};
|
|
59
46
|
export default AdmittedClients;
|
|
@@ -60,16 +60,8 @@ export const AlertDestinations = ({ errorState, orgUid, readOnly = false, }) =>
|
|
|
60
60
|
return (_jsx(McpErrorBlankslate, { state: errorState(settings.error, 'Alert destinations'), onRetry: () => settings.refetch() }));
|
|
61
61
|
}
|
|
62
62
|
if (settings.isPending && settings.data === undefined) {
|
|
63
|
-
return (_jsx(Box, {
|
|
63
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", py: 4, children: _jsx(Spinner, {}) }));
|
|
64
64
|
}
|
|
65
|
-
return (_jsxs(Box, { sx: {
|
|
66
|
-
display: 'grid',
|
|
67
|
-
gap: 3,
|
|
68
|
-
maxWidth: '42rem',
|
|
69
|
-
p: 3,
|
|
70
|
-
border: '1px solid',
|
|
71
|
-
borderColor: 'border.default',
|
|
72
|
-
borderRadius: 2,
|
|
73
|
-
}, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: "Where alerts go" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Set once for the organization, not per rule \u2014 a URL repeated on twenty rules is nineteen places to forget when it rotates. Every firing also appears in the app, and that copy is sent first so it is not lost when one of these is down." })] }), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Email" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertEmails, onChange: event => set('alertEmails', event.target.value), placeholder: "ops@example.co, oncall@example.co" }), _jsx(FormControl.Caption, { children: "Comma-separated, up to twenty. An alert list is the people who want to know, not a mailing list \u2014 a firing that emails two hundred people is a firing that gets the rule switched off." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Slack" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertSlackWebhookUrl, onChange: event => set('alertSlackWebhookUrl', event.target.value), placeholder: "https://hooks.slack.com/services/\u2026" }), _jsx(FormControl.Caption, { children: "A Slack incoming webhook, and only Slack\u2019s \u2014 anything else belongs in the field below. Through this one Slack gets a coloured attachment with the reading, the severity and the scope as separate fields; through the generic webhook it shows one grey line." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Webhook" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertWebhookUrl, onChange: event => set('alertWebhookUrl', event.target.value), placeholder: "https://events.pagerduty.com/\u2026" }), _jsxs(FormControl.Caption, { children: ["Anything that takes a JSON POST: PagerDuty, a chat relay, your own endpoint. ", _jsx("strong", { children: "https only" }), " \u2014 an alert names your organization and what its agents did. It carries no credential."] })] }), !readOnly && (_jsxs(Box, { sx: { display: 'flex', gap: 2 }, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save destinations" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" }))] })), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Each copy is attempted on its own: if the webhook is down you still get the email. A copy that fails is recorded and", ' ', _jsx("strong", { children: "not retried" }), " \u2014 retrying a notice against an endpoint that is down turns its outage into a loop against it, and the alert is already in the app."] })] }));
|
|
65
|
+
return (_jsxs(Box, { display: "grid", gap: 3, maxWidth: "42rem", p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: "Where alerts go" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Set once for the organization, not per rule \u2014 a URL repeated on twenty rules is nineteen places to forget when it rotates. Every firing also appears in the app, and that copy is sent first so it is not lost when one of these is down." })] }), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Email" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertEmails, onChange: event => set('alertEmails', event.target.value), placeholder: "ops@example.co, oncall@example.co" }), _jsx(FormControl.Caption, { children: "Comma-separated, up to twenty. An alert list is the people who want to know, not a mailing list \u2014 a firing that emails two hundred people is a firing that gets the rule switched off." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Slack" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertSlackWebhookUrl, onChange: event => set('alertSlackWebhookUrl', event.target.value), placeholder: "https://hooks.slack.com/services/\u2026" }), _jsx(FormControl.Caption, { children: "A Slack incoming webhook, and only Slack\u2019s \u2014 anything else belongs in the field below. Through this one Slack gets a coloured attachment with the reading, the severity and the scope as separate fields; through the generic webhook it shows one grey line." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Webhook" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertWebhookUrl, onChange: event => set('alertWebhookUrl', event.target.value), placeholder: "https://events.pagerduty.com/\u2026" }), _jsxs(FormControl.Caption, { children: ["Anything that takes a JSON POST: PagerDuty, a chat relay, your own endpoint. ", _jsx("strong", { children: "https only" }), " \u2014 an alert names your organization and what its agents did. It carries no credential."] })] }), !readOnly && (_jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save destinations" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" }))] })), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Each copy is attempted on its own: if the webhook is down you still get the email. A copy that fails is recorded and", ' ', _jsx("strong", { children: "not retried" }), " \u2014 retrying a notice against an endpoint that is down turns its outage into a loop against it, and the alert is already in the app."] })] }));
|
|
74
66
|
};
|
|
75
67
|
export default AlertDestinations;
|