@datalayer/core 1.2.18 → 1.2.20
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.d.ts +10 -11
- package/lib/components/avatars/UserAvatar.js +23 -52
- package/lib/components/avatars/UserProfileAvatar.js +1 -1
- package/lib/components/avatars/avatarChoice.d.ts +42 -0
- package/lib/components/avatars/avatarChoice.js +65 -0
- package/lib/components/avatars/index.d.ts +1 -0
- package/lib/components/avatars/index.js +1 -0
- package/lib/components/billing/BillingEntitySelect.js +6 -48
- package/lib/components/checkout/StripeCheckout.js +33 -217
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +3 -4
- package/lib/components/collaboration/LiveEditorCollaborators.js +7 -5
- 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/emoji/faceEmojis.js +48 -0
- package/lib/components/emoji/fluent/loaders.js +46 -0
- package/lib/components/emoji/fluent/svg/1f30a.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f30a.js +5 -0
- package/lib/components/emoji/fluent/svg/1f310.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f310.js +5 -0
- package/lib/components/emoji/fluent/svg/1f324.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f324.js +5 -0
- package/lib/components/emoji/fluent/svg/1f326.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f326.js +5 -0
- package/lib/components/emoji/fluent/svg/1f327.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f327.js +5 -0
- package/lib/components/emoji/fluent/svg/1f33e.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f33e.js +5 -0
- package/lib/components/emoji/fluent/svg/1f372.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f372.js +5 -0
- package/lib/components/emoji/fluent/svg/1f373.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f373.js +5 -0
- package/lib/components/emoji/fluent/svg/1f39b.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f39b.js +5 -0
- package/lib/components/emoji/fluent/svg/1f3ab.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f3ab.js +5 -0
- package/lib/components/emoji/fluent/svg/1f41b.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f41b.js +5 -0
- package/lib/components/emoji/fluent/svg/1f441.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f441.js +5 -0
- package/lib/components/emoji/fluent/svg/1f465.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f465.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4b9.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4b9.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4be.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4be.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4c1.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4c1.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4c2.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4c2.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4c4.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4c4.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4d3.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4d3.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4d6.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4d6.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4dc.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4dc.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4e1.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4e1.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4e2.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4e2.js +5 -0
- package/lib/components/emoji/fluent/svg/1f4e7.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f4e7.js +5 -0
- package/lib/components/emoji/fluent/svg/1f500.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f500.js +5 -0
- package/lib/components/emoji/fluent/svg/1f501.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f501.js +5 -0
- package/lib/components/emoji/fluent/svg/1f504.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f504.js +5 -0
- package/lib/components/emoji/fluent/svg/1f52d.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f52d.js +5 -0
- package/lib/components/emoji/fluent/svg/1f552.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f552.js +5 -0
- package/lib/components/emoji/fluent/svg/1f58b.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f58b.js +5 -0
- package/lib/components/emoji/fluent/svg/1f5b2.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f5b2.js +5 -0
- package/lib/components/emoji/fluent/svg/1f5c3.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f5c3.js +5 -0
- package/lib/components/emoji/fluent/svg/1f5d1.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f5d1.js +5 -0
- package/lib/components/emoji/fluent/svg/1f5dc.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f5dc.js +5 -0
- package/lib/components/emoji/fluent/svg/1f604.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f604.js +5 -0
- package/lib/components/emoji/fluent/svg/1f917.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f917.js +5 -0
- package/lib/components/emoji/fluent/svg/1f9d1-200d-2696.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1f9d1-200d-2696.js +5 -0
- package/lib/components/emoji/fluent/svg/1fa79.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1fa79.js +5 -0
- package/lib/components/emoji/fluent/svg/1fa9d.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1fa9d.js +5 -0
- package/lib/components/emoji/fluent/svg/1fa9f.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/1fa9f.js +5 -0
- package/lib/components/emoji/fluent/svg/23f3.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/23f3.js +5 -0
- package/lib/components/emoji/fluent/svg/2611.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/2611.js +5 -0
- package/lib/components/emoji/fluent/svg/2702.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/2702.js +5 -0
- package/lib/components/emoji/fluent/svg/270f.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/270f.js +5 -0
- package/lib/components/emoji/fluent/svg/2728.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/2728.js +5 -0
- package/lib/components/emoji/fluent/svg/2b1c.d.ts +2 -0
- package/lib/components/emoji/fluent/svg/2b1c.js +5 -0
- 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.d.ts +24 -1
- package/lib/components/principal/PrincipalAppearance.js +107 -80
- package/lib/components/principal/PrincipalAvatar.d.ts +5 -7
- package/lib/components/principal/PrincipalAvatar.js +12 -38
- package/lib/components/principal/PrincipalBadge.js +3 -13
- package/lib/components/principal/PrincipalBanner.js +4 -32
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +4 -7
- package/lib/components/principal/PrincipalDetailsOverlay.js +8 -24
- 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/unlinkIAMProvider.d.ts +22 -0
- package/lib/hooks/unlinkIAMProvider.js +24 -0
- package/lib/hooks/useAuthorization.js +6 -1
- package/lib/hooks/useCache.d.ts +8 -1
- package/lib/hooks/useCache.js +35 -4
- package/lib/hooks/useNavigate.js +8 -2
- 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/IAMProviderLinked.d.ts +9 -0
- package/lib/models/IAMProviderLinked.js +34 -5
- package/lib/models/McpTask.d.ts +18 -1
- package/lib/models/McpTask.js +15 -0
- package/lib/models/Person.d.ts +9 -0
- package/lib/models/Person.js +23 -0
- package/lib/models/Profile.d.ts +2 -2
- package/lib/models/User.js +5 -12
- package/lib/models/UserOnboarding.d.ts +9 -0
- package/lib/models/UserOnboarding.js +33 -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 +9 -0
- package/lib/portable/index.js +14 -0
- package/lib/portable/states.d.ts +39 -0
- package/lib/portable/states.js +71 -0
- package/lib/state/substates/NavigationState.d.ts +10 -6
- package/lib/state/substates/NavigationState.js +18 -8
- 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 +3 -11
- package/lib/views/mcp/AlertRules.js +4 -9
- package/lib/views/mcp/ApprovalQueue.js +5 -22
- package/lib/views/mcp/AuditLog.js +7 -44
- package/lib/views/mcp/ConnectedAgents.js +1 -6
- package/lib/views/mcp/EnterpriseConsole.js +11 -24
- package/lib/views/mcp/IdentityProviders.js +7 -31
- package/lib/views/mcp/McpDashboard.js +15 -31
- package/lib/views/mcp/McpHome.js +8 -55
- package/lib/views/mcp/McpObservability.js +14 -86
- package/lib/views/mcp/NotebookRuns.js +6 -13
- package/lib/views/mcp/OrganizationPolicy.js +3 -9
- package/lib/views/mcp/OrganizationUsage.js +5 -12
- package/lib/views/mcp/PersonalPolicy.js +3 -3
- package/lib/views/mcp/Policies.js +6 -47
- package/lib/views/mcp/PolicyForm.js +1 -9
- package/lib/views/mcp/PolicyHistory.js +2 -8
- package/lib/views/mcp/RunDetail.d.ts +1 -1
- package/lib/views/mcp/RunDetail.js +7 -26
- package/lib/views/mcp/ScimProvisioning.js +4 -16
- package/lib/views/mcp/ServiceAgents.js +4 -17
- package/lib/views/mcp/TeamPolicies.js +7 -19
- package/lib/views/mcp/ToolAccess.js +16 -23
- 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
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { Heading, Label, ProgressBar, Spinner, Text } from '@primer/react';
|
|
4
4
|
import { Blankslate, DataTable, Table } from '@primer/react/experimental';
|
|
5
|
-
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
6
6
|
import { GraphIcon } from '@primer/octicons-react';
|
|
7
7
|
import { ClientBadge, McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
8
|
import { useOrgMcpUsage } from '../../hooks/useMcp.js';
|
|
@@ -69,29 +69,22 @@ const QuotaCard = ({ label, hint, quota, }) => {
|
|
|
69
69
|
const limit = quota?.limit;
|
|
70
70
|
const used = quota?.used;
|
|
71
71
|
const fraction = quota?.fraction;
|
|
72
|
-
return (_jsxs(Box, { sx: {
|
|
73
|
-
border: '1px solid',
|
|
74
|
-
borderColor: 'border.default',
|
|
75
|
-
borderRadius: 2,
|
|
76
|
-
p: 3,
|
|
77
|
-
flex: '1 1 220px',
|
|
78
|
-
minWidth: 0,
|
|
79
|
-
}, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: label }), _jsx(Box, { sx: { mt: 2, display: 'flex', alignItems: 'baseline', gap: 2 }, children: quota?.unknown ? (_jsx(Text, { sx: { fontSize: 2, color: 'fg.muted' }, children: "Not available" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 4, fontWeight: 'bold' }, children: typeof used === 'number' ? amount(used) : '—' }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: typeof limit === 'number'
|
|
72
|
+
return (_jsxs(Card, { border: true, shadow: "none", display: "block", p: 3, flex: "1 1 220px", minWidth: 0, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: label }), _jsx(Box, { mt: 2, display: "flex", alignItems: "baseline", gap: 2, children: quota?.unknown ? (_jsx(Text, { sx: { fontSize: 2, color: 'fg.muted' }, children: "Not available" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 4, fontWeight: 'bold' }, children: typeof used === 'number' ? amount(used) : '—' }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: typeof limit === 'number'
|
|
80
73
|
? `of ${amount(limit)}`
|
|
81
|
-
: 'no limit set' })] })) }), typeof fraction === 'number' && (_jsx(Box, {
|
|
74
|
+
: 'no limit set' })] })) }), typeof fraction === 'number' && (_jsx(Box, { mt: 2, children: _jsx(ProgressBar, { progress: Math.min(100, fraction * 100), barSize: "small", bg: `${quotaTone(fraction)}.emphasis`, "aria-label": `${label}: ${Math.round(fraction * 100)}% of the limit` }) })), _jsx(Text, { as: "p", sx: { mt: 2, mb: 0, fontSize: 0, color: 'fg.muted' }, children: quota?.unknown ? quota.unknown : hint })] }));
|
|
82
75
|
};
|
|
83
76
|
export const OrganizationUsage = ({ errorState, orgUid, teamUid, showTitle = true, }) => {
|
|
84
77
|
const usage = useOrgMcpUsage(orgUid, teamUid ? { team: teamUid } : {});
|
|
85
78
|
const rows = useMemo(() => spendRowsOf(usage.data?.byAgent ?? []), [usage.data]);
|
|
86
79
|
if (usage.isLoading) {
|
|
87
|
-
return (_jsx(Box, {
|
|
80
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", p: 6, children: _jsx(Spinner, {}) }));
|
|
88
81
|
}
|
|
89
82
|
if (usage.isError) {
|
|
90
83
|
return (_jsx(McpErrorBlankslate, { state: errorState(usage.error, 'The usage'), onRetry: () => void usage.refetch() }));
|
|
91
84
|
}
|
|
92
85
|
const quotas = usage.data?.quotas ?? {};
|
|
93
86
|
const breakdown = breakdownState(usage.data);
|
|
94
|
-
return (_jsxs(Box, { children: [showTitle && (_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Usage" })), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0, mb: 3 }, children: "What this organization's agents used over the last day, against what they may use. Limits are set in the organization's MCP policy, not here." }), _jsx(Box, {
|
|
87
|
+
return (_jsxs(Box, { children: [showTitle && (_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Usage" })), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0, mb: 3 }, children: "What this organization's agents used over the last day, against what they may use. Limits are set in the organization's MCP policy, not here." }), _jsx(Box, { display: "flex", flexWrap: "wrap", gap: 3, children: QUOTAS.map(quota => (_jsx(QuotaCard, { label: quota.label, hint: quota.hint, quota: quotas[quota.key] }, quota.key))) }), _jsx(Heading, { as: "h3", sx: { fontSize: 2, mt: 4, mb: 2 }, children: "Credits by agent" }), breakdown === 'unreadable' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The day's spend could not be read" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "A breakdown of a total nobody could read would make every agent look cheap. Try again in a moment." }) })] })) : breakdown === 'empty' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent spent credits today" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "Work a person ran themselves is in the total above, and is not an agent's." }) })] })) : (_jsx(Table.Container, { children: _jsx(DataTable, { "aria-labelledby": "usage-by-agent", data: rows, columns: [
|
|
95
88
|
{
|
|
96
89
|
header: 'Agent',
|
|
97
90
|
field: 'clientId',
|
|
@@ -109,12 +109,12 @@ export const PersonalPolicy = ({ errorState, userUid, organizations = [], showTi
|
|
|
109
109
|
return (_jsx(McpErrorBlankslate, { state: errorState(layer.error, 'Your MCP policy'), onRetry: () => layer.refetch() }));
|
|
110
110
|
}
|
|
111
111
|
if (layer.isPending && layer.data === undefined) {
|
|
112
|
-
return (_jsx(Box, {
|
|
112
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) }));
|
|
113
113
|
}
|
|
114
|
-
return (_jsxs(Box, {
|
|
114
|
+
return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, maxWidth: "52rem", children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Your MCP policy" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: ["Rules for your own agents. These can only make things", ' ', _jsx("strong", { children: "stricter" }), " \u2014 where you belong to an organization, its rules apply underneath and nothing here can loosen them."] })] })), conflict && (_jsxs(Flash, { variant: "warning", children: [_jsx(Text, { sx: { fontSize: 1 }, children: conflict }), _jsx(Box, { mt: 2, children: _jsx(Button, { size: "small", onClick: () => {
|
|
115
115
|
setConflict('');
|
|
116
116
|
layer.refetch();
|
|
117
|
-
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, notes: notes, belowAllowedClients: _jsx(AdmittedClients, { scope: "personal", subjectUid: userUid, edited: draft.allowedClients !== stored.allowedClients }) }), _jsxs(Box, {
|
|
117
|
+
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, notes: notes, belowAllowedClients: _jsx(AdmittedClients, { scope: "personal", subjectUid: userUid, edited: draft.allowedClients !== stored.allowedClients }) }), _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { marginLeft: "auto", children: _jsx(Button, { variant: "danger", disabled: remove.isPending, onClick: () => remove.mutate(undefined, {
|
|
118
118
|
onSuccess: () => enqueueToast('Removed. You narrow nothing of your own.', {
|
|
119
119
|
variant: 'success',
|
|
120
120
|
}),
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
3
|
import { Heading, Label, Link, Select, Spinner, Text } from '@primer/react';
|
|
4
4
|
import { Blankslate, Table } from '@primer/react/experimental';
|
|
5
|
-
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
6
6
|
import { ShieldCheckIcon } from '@primer/octicons-react';
|
|
7
7
|
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
8
|
import { useConnectedAgents, useEffectivePolicy } from '../../hooks/useMcp.js';
|
|
@@ -108,7 +108,7 @@ export const ruleValueLabel = (value) => {
|
|
|
108
108
|
return String(value);
|
|
109
109
|
};
|
|
110
110
|
/** The tools of one access kind, and what happens before they run. */
|
|
111
|
-
const ToolGroup = ({ title, description, tools, organizationName, }) => (_jsxs(Box, {
|
|
111
|
+
const ToolGroup = ({ title, description, tools, organizationName, }) => (_jsxs(Box, { display: "grid", gap: 2, children: [_jsxs(Box, { children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold', display: 'block' }, children: title }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: description })] }), tools.length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "No tool of this kind is available to your agents." })) : (_jsx(Box, { display: "flex", gap: 1, flexWrap: "wrap", children: tools.map(tool => (_jsx(Label, { size: "small", variant: !tool.allowed
|
|
112
112
|
? 'danger'
|
|
113
113
|
: tool.approval === 'explicit'
|
|
114
114
|
? 'attention'
|
|
@@ -155,56 +155,15 @@ export const Policies = ({ errorState, routes, organizationName, showTitle = tru
|
|
|
155
155
|
if (policy.isError) {
|
|
156
156
|
return (_jsx(McpErrorBlankslate, { state: errorState(policy.error, 'Policies'), onRetry: () => policy.refetch() }));
|
|
157
157
|
}
|
|
158
|
-
return (_jsxs(Box, {
|
|
158
|
+
return (_jsxs(Box, { display: "grid", gap: 4, minWidth: 0, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Policies" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: previewAs
|
|
159
159
|
? 'What applies to this agent, and which layer decided it.'
|
|
160
|
-
: 'What applies to your agents, and which layer decided it.' })] })), (agents.data?.length ?? 0) > 1 && (_jsxs(Box, { sx: {
|
|
161
|
-
display: 'flex',
|
|
162
|
-
alignItems: 'center',
|
|
163
|
-
gap: 2,
|
|
164
|
-
flexWrap: 'wrap',
|
|
165
|
-
}, children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Preview as" }), _jsx(Box, { sx: { minWidth: '18rem' }, children: _jsxs(Select, { value: previewAs, onChange: event => setPreviewAs(event.target.value), "aria-label": "Preview the policy as", children: [_jsx(Select.Option, { value: "", children: "You \u2014 your own token" }), (agents.data ?? []).map(agent => (_jsx(Select.Option, { value: agent.clientId, children: agent.clientName || agent.clientId }, agent.uid)))] }) }), previewAs && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "A grant carries its own scopes and its own admitted clients, so this can differ from what applies to you." }))] })), policy.isPending && !policy.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 6 }, children: _jsx(Spinner, {}) })) : rows.length === 0 && tools.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(ShieldCheckIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The defaults apply" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No agent is connected yet, so nothing narrows the Datalayer defaults. Connect a client and this page fills with what its calls are allowed to do." }) }), _jsx(Link, { as: "button", sx: { cursor: 'pointer' }, onClick: () => navigate(routes.access), children: "Set up a client" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "What applies to your agents" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Scope of this reading: ", policy.data?.scope ?? 'personal', policy.data?.evaluatedAt
|
|
160
|
+
: 'What applies to your agents, and which layer decided it.' })] })), (agents.data?.length ?? 0) > 1 && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Preview as" }), _jsx(Box, { minWidth: "18rem", children: _jsxs(Select, { value: previewAs, onChange: event => setPreviewAs(event.target.value), "aria-label": "Preview the policy as", children: [_jsx(Select.Option, { value: "", children: "You \u2014 your own token" }), (agents.data ?? []).map(agent => (_jsx(Select.Option, { value: agent.clientId, children: agent.clientName || agent.clientId }, agent.uid)))] }) }), previewAs && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "A grant carries its own scopes and its own admitted clients, so this can differ from what applies to you." }))] })), policy.isPending && !policy.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 6, children: _jsx(Spinner, {}) })) : rows.length === 0 && tools.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(ShieldCheckIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The defaults apply" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No agent is connected yet, so nothing narrows the Datalayer defaults. Connect a client and this page fills with what its calls are allowed to do." }) }), _jsx(Link, { as: "button", sx: { cursor: 'pointer' }, onClick: () => navigate(routes.access), children: "Set up a client" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "What applies to your agents" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Scope of this reading: ", policy.data?.scope ?? 'personal', policy.data?.evaluatedAt
|
|
166
161
|
? ` · evaluated ${policy.data.evaluatedAt}`
|
|
167
|
-
: ''] })] }), _jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h4", id: "effective-policy", children: "Effective policy" }), _jsx(Table.Subtitle, { as: "p", id: "effective-policy-subtitle", children: "Each layer only narrows the one above it, so the strictest rule is what applies." }), _jsxs(Box, { as: "table", "aria-labelledby": "effective-policy", "aria-describedby": "effective-policy-subtitle",
|
|
168
|
-
textAlign: 'left',
|
|
169
|
-
fontSize: 0,
|
|
170
|
-
color: 'fg.muted',
|
|
171
|
-
fontWeight: 'semibold',
|
|
172
|
-
py: 2,
|
|
173
|
-
px: 2,
|
|
174
|
-
borderBottom: '1px solid',
|
|
175
|
-
borderColor: 'border.default',
|
|
176
|
-
}, children: header }, header))) }) }), _jsx(Box, { as: "tbody", children: rows.map(row => (_jsxs(Box, { as: "tr", children: [_jsxs(Box, { as: "td", sx: {
|
|
177
|
-
py: 2,
|
|
178
|
-
px: 2,
|
|
179
|
-
borderBottom: '1px solid',
|
|
180
|
-
borderColor: 'border.muted',
|
|
181
|
-
verticalAlign: 'top',
|
|
182
|
-
}, children: [_jsx(Text, { sx: { fontSize: 1, display: 'block' }, children: row.label }), row.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.description }))] }), _jsx(Box, { as: "td", sx: {
|
|
183
|
-
py: 2,
|
|
184
|
-
px: 2,
|
|
185
|
-
borderBottom: '1px solid',
|
|
186
|
-
borderColor: 'border.muted',
|
|
187
|
-
verticalAlign: 'top',
|
|
188
|
-
}, children: _jsx(Text, { sx: { fontSize: 1 }, children: ruleValueLabel(row.rule.value) }) }), _jsxs(Box, { as: "td", sx: {
|
|
189
|
-
py: 2,
|
|
190
|
-
px: 2,
|
|
191
|
-
borderBottom: '1px solid',
|
|
192
|
-
borderColor: 'border.muted',
|
|
193
|
-
verticalAlign: 'top',
|
|
194
|
-
whiteSpace: 'nowrap',
|
|
195
|
-
}, children: [_jsx(Label, { size: "small", variant: LAYER_VARIANT[row.rule.decidedBy], children: layerLabel(row.rule.decidedBy, organizationName) }), row.rule.reason && (_jsx(Text, { sx: {
|
|
162
|
+
: ''] })] }), _jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h4", id: "effective-policy", children: "Effective policy" }), _jsx(Table.Subtitle, { as: "p", id: "effective-policy-subtitle", children: "Each layer only narrows the one above it, so the strictest rule is what applies." }), _jsxs(Box, { as: "table", "aria-labelledby": "effective-policy", "aria-describedby": "effective-policy-subtitle", width: "100%", borderCollapse: "collapse", children: [_jsx(Box, { as: "thead", children: _jsx(Box, { as: "tr", children: ['Rule', 'What applies', 'Decided by'].map(header => (_jsx(Box, { as: "th", textAlign: "left", fontSize: 0, color: "fg.muted", fontWeight: "semibold", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.default", children: header }, header))) }) }), _jsx(Box, { as: "tbody", children: rows.map(row => (_jsxs(Box, { as: "tr", children: [_jsxs(Box, { as: "td", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.muted", verticalAlign: "top", children: [_jsx(Text, { sx: { fontSize: 1, display: 'block' }, children: row.label }), row.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.description }))] }), _jsx(Box, { as: "td", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.muted", verticalAlign: "top", children: _jsx(Text, { sx: { fontSize: 1 }, children: ruleValueLabel(row.rule.value) }) }), _jsxs(Box, { as: "td", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.muted", verticalAlign: "top", whiteSpace: "nowrap", children: [_jsx(Label, { size: "small", variant: LAYER_VARIANT[row.rule.decidedBy], children: layerLabel(row.rule.decidedBy, organizationName) }), row.rule.reason && (_jsx(Text, { sx: {
|
|
196
163
|
fontSize: 0,
|
|
197
164
|
color: 'fg.muted',
|
|
198
165
|
display: 'block',
|
|
199
166
|
mt: 1,
|
|
200
|
-
}, children: row.rule.reason }))] })] }, row.name))) })] })] }), _jsxs(Box, {
|
|
201
|
-
p: 3,
|
|
202
|
-
border: '1px solid',
|
|
203
|
-
borderColor: 'border.default',
|
|
204
|
-
borderRadius: 2,
|
|
205
|
-
bg: 'canvas.subtle',
|
|
206
|
-
display: 'grid',
|
|
207
|
-
gap: 1,
|
|
208
|
-
}, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: "Your own rules" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Narrowing the defaults for your own agents \u2014 approval by tool, a daily credits cap, a denylist, the environments they may use \u2014 is edited here once the policy layers ship. When your agents act inside an organization, that organization's policy applies instead of yours." }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold', mt: 2 }, children: "Organizations you own" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "The policy form for each organization you own, with its history and a preview of what one agent would get, is the same form the organization's MCP tab carries. It joins this page with the layers." })] })] }));
|
|
167
|
+
}, children: row.rule.reason }))] })] }, row.name))) })] })] }), _jsxs(Box, { display: "grid", gap: 3, children: [_jsx(ToolGroup, { title: "Read-only tools", description: "Tools that only look: listing, reading a cell, reading a source.", tools: readOnlyTools, organizationName: organizationName }), _jsx(ToolGroup, { title: "Tools that change things", description: "Tools that write to a notebook, a document or a source.", tools: writeTools, organizationName: organizationName }), _jsx(ToolGroup, { title: "Tools that launch or spend", description: "Tools that execute code, launch a sandbox or reserve compute.", tools: executeTools, organizationName: organizationName })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Your token carries ", policy.data?.scopes.length ?? 0, " scopes, which is the ceiling over every rule above:", ' ', (policy.data?.scopes ?? []).join(', ') || 'none', "."] })] })), _jsxs(Card, { border: true, shadow: "none", variant: "subtle", p: 3, display: "grid", gap: 1, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: "Your own rules" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Narrowing the defaults for your own agents \u2014 approval by tool, a daily credits cap, a denylist, the environments they may use \u2014 is edited here once the policy layers ship. When your agents act inside an organization, that organization's policy applies instead of yours." }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold', mt: 2 }, children: "Organizations you own" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "The policy form for each organization you own, with its history and a preview of what one agent would get, is the same form the organization's MCP tab carries. It joins this page with the layers." })] })] }));
|
|
209
168
|
};
|
|
210
169
|
export default Policies;
|
|
@@ -101,13 +101,5 @@ export const rulesFrom = (draft) => {
|
|
|
101
101
|
};
|
|
102
102
|
export const policyChanged = (a, b) => Object.keys(EMPTY_POLICY_DRAFT).some(key => a[key] !== b[key]);
|
|
103
103
|
const Note = ({ children, }) => children ? (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'attention.fg', mt: 1, mb: 0 }, children: children })) : null;
|
|
104
|
-
export const PolicyForm = ({ draft, onChange, disabled = false, notes = {}, belowAllowedClients, }) => (_jsxs(Box, {
|
|
105
|
-
display: 'grid',
|
|
106
|
-
gap: 3,
|
|
107
|
-
gridTemplateColumns: ['1fr', '1fr 1fr 1fr'],
|
|
108
|
-
}, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Calls per minute" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCallsPerMinute, onChange: event => onChange('maxCallsPerMinute', event.target.value) }), _jsx(FormControl.Caption, { children: "Empty for no limit." }), _jsx(Note, { children: notes.maxCallsPerMinute })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Credits per day" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCreditsPerDay, onChange: event => onChange('maxCreditsPerDay', event.target.value) }), _jsx(FormControl.Caption, { children: "Checked before a launch, never mid-session." }), _jsx(Note, { children: notes.maxCreditsPerDay })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Sandboxes at once" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxConcurrentSandboxes, onChange: event => onChange('maxConcurrentSandboxes', event.target.value) }), _jsx(FormControl.Caption, { children: "Counted across the layer that set it." }), _jsx(Note, { children: notes.maxConcurrentSandboxes })] })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "GPU-hours per month" }), _jsx(TextInput, { block: true, type: "number", min: 0, step: "any", disabled: disabled, value: draft.gpuHoursPerMonth, onChange: event => onChange('gpuHoursPerMonth', event.target.value) }), _jsxs(FormControl.Caption, { children: ["Empty for no limit. Refuses a launch on an environment that", ' ', _jsx("strong", { children: "has a GPU" }), " \u2014 a CPU sandbox is never affected, whatever this reads. Fractions are allowed: half an hour on one card is 0.5.", _jsx("br", {}), "Over the ", _jsx("strong", { children: "last 30 days" }), ", not the calendar month, so a limit cannot be spent twice across a reset \u2014 which means it will not line up with an invoice period.", ' ', _jsx("strong", { children: "Every GPU-hour billed to this scope counts, not only your agents\u2019" }), ' ', "\u2014 a notebook somebody ran by hand is in this number too."] }), _jsx(Note, { children: notes.gpuHoursPerMonth })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Storage" }), _jsx(TextInput, { block: true, type: "number", min: 0, step: 1, disabled: disabled, value: draft.storageBytes, onChange: event => onChange('storageBytes', event.target.value), trailingVisual: "bytes" }), _jsxs(FormControl.Caption, { children: ["Empty for no limit. In ", _jsx("strong", { children: "bytes" }), " \u2014 1 GB is 1073741824, 1 TB is 1099511627776. A number whose unit is implied is one somebody eventually reads as the other, and this is compared against a figure reported in bytes.", _jsx("br", {}), "Checked on ", _jsx("strong", { children: "every" }), " launch, not only one that attaches content: every sandbox can write.", ' ', _jsx("strong", { children: "Every kept version counts" }), " \u2014 an object\u2019s size is its newest version\u2019s, so a hundred versions of a 1\u00A0GB file are 100\u00A0GB, and deleting the newest frees none of the rest."] }), _jsx(Note, { children: notes.storageBytes })] }), _jsxs(Box, { sx: {
|
|
109
|
-
display: 'grid',
|
|
110
|
-
gap: 3,
|
|
111
|
-
gridTemplateColumns: ['1fr', '1fr 1fr'],
|
|
112
|
-
}, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Session at most" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.sessionMaxHours, onChange: event => onChange('sessionMaxHours', event.target.value) }), _jsx(FormControl.Caption, { children: "Hours before a person signs in and consents again. It bites at the refresh: a token already minted lives out its own short life, and the connection ends when the client next tries to extend it. Empty for no limit." }), _jsx(Note, { children: notes.sessionMaxHours })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Your directory may stand in for the consent screen" }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mt: 1 }, children: [_jsx(ToggleSwitch, { size: "small", checked: draft.ssoAdmitsWithoutConsent === 'true', disabled: disabled, "aria-labelledby": "sso-admits-without-consent-label", onClick: () => onChange('ssoAdmitsWithoutConsent', draft.ssoAdmitsWithoutConsent === 'true' ? '' : 'true') }), _jsx(Text, { id: "sso-admits-without-consent-label", sx: { fontSize: 1 }, children: "Skip it for clients you already admit" })] }), _jsxs(FormControl.Caption, { children: ["Only for somebody who signed in through one of", ' ', _jsx("strong", { children: "your own" }), " enabled identity providers, and only for a client named in ", _jsx("strong", { children: "Admitted clients" }), " above \u2014 which must not be empty. Every other session still sees the screen. This does not require anyone to sign in through your directory; it only says the screen adds nothing when they did."] }), _jsx(Note, { children: notes.ssoAdmitsWithoutConsent })] })] })] }));
|
|
104
|
+
export const PolicyForm = ({ draft, onChange, disabled = false, notes = {}, belowAllowedClients, }) => (_jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Denied tools" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.toolDenylist, onChange: event => onChange('toolDenylist', event.target.value), placeholder: 'execute_cell\ndelete_file' }), _jsx(FormControl.Caption, { children: "One a line. A denial is added to every other layer\u2019s and can never be lifted by one." }), _jsx(Note, { children: notes.toolDenylist })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Permitted tools" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.toolAllowlist, onChange: event => onChange('toolAllowlist', event.target.value), placeholder: 'read_cell\nlist_notebooks' }), _jsxs(FormControl.Caption, { children: ["One a line, and everything else is refused.", ' ', _jsx("strong", { children: "Leave it empty to set no allowlist" }), " \u2014 empty means \u201CI have not set one\u201D, not \u201Cnothing is permitted\u201D, because the second would stop every agent at once."] }), _jsx(Note, { children: notes.toolAllowlist })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Admitted clients" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.allowedClients, onChange: event => onChange('allowedClients', event.target.value), placeholder: 'https://claude.ai/.well-known/mcp-client.json\ncursor.com' }), _jsx(FormControl.Caption, { children: "A client\u2019s document URL, or just its hostname. Empty is not an allowlist, for the same reason as above." }), _jsx(Note, { children: notes.allowedClients }), belowAllowedClients && _jsx(Box, { mt: 2, children: belowAllowedClients })] }), _jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', '1fr 1fr 1fr'], children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Calls per minute" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCallsPerMinute, onChange: event => onChange('maxCallsPerMinute', event.target.value) }), _jsx(FormControl.Caption, { children: "Empty for no limit." }), _jsx(Note, { children: notes.maxCallsPerMinute })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Credits per day" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCreditsPerDay, onChange: event => onChange('maxCreditsPerDay', event.target.value) }), _jsx(FormControl.Caption, { children: "Checked before a launch, never mid-session." }), _jsx(Note, { children: notes.maxCreditsPerDay })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Sandboxes at once" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxConcurrentSandboxes, onChange: event => onChange('maxConcurrentSandboxes', event.target.value) }), _jsx(FormControl.Caption, { children: "Counted across the layer that set it." }), _jsx(Note, { children: notes.maxConcurrentSandboxes })] })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "GPU-hours per month" }), _jsx(TextInput, { block: true, type: "number", min: 0, step: "any", disabled: disabled, value: draft.gpuHoursPerMonth, onChange: event => onChange('gpuHoursPerMonth', event.target.value) }), _jsxs(FormControl.Caption, { children: ["Empty for no limit. Refuses a launch on an environment that", ' ', _jsx("strong", { children: "has a GPU" }), " \u2014 a CPU sandbox is never affected, whatever this reads. Fractions are allowed: half an hour on one card is 0.5.", _jsx("br", {}), "Over the ", _jsx("strong", { children: "last 30 days" }), ", not the calendar month, so a limit cannot be spent twice across a reset \u2014 which means it will not line up with an invoice period.", ' ', _jsx("strong", { children: "Every GPU-hour billed to this scope counts, not only your agents\u2019" }), ' ', "\u2014 a notebook somebody ran by hand is in this number too."] }), _jsx(Note, { children: notes.gpuHoursPerMonth })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Storage" }), _jsx(TextInput, { block: true, type: "number", min: 0, step: 1, disabled: disabled, value: draft.storageBytes, onChange: event => onChange('storageBytes', event.target.value), trailingVisual: "bytes" }), _jsxs(FormControl.Caption, { children: ["Empty for no limit. In ", _jsx("strong", { children: "bytes" }), " \u2014 1 GB is 1073741824, 1 TB is 1099511627776. A number whose unit is implied is one somebody eventually reads as the other, and this is compared against a figure reported in bytes.", _jsx("br", {}), "Checked on ", _jsx("strong", { children: "every" }), " launch, not only one that attaches content: every sandbox can write.", ' ', _jsx("strong", { children: "Every kept version counts" }), " \u2014 an object\u2019s size is its newest version\u2019s, so a hundred versions of a 1\u00A0GB file are 100\u00A0GB, and deleting the newest frees none of the rest."] }), _jsx(Note, { children: notes.storageBytes })] }), _jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', '1fr 1fr'], children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Session at most" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.sessionMaxHours, onChange: event => onChange('sessionMaxHours', event.target.value) }), _jsx(FormControl.Caption, { children: "Hours before a person signs in and consents again. It bites at the refresh: a token already minted lives out its own short life, and the connection ends when the client next tries to extend it. Empty for no limit." }), _jsx(Note, { children: notes.sessionMaxHours })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Your directory may stand in for the consent screen" }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, mt: 1, children: [_jsx(ToggleSwitch, { size: "small", checked: draft.ssoAdmitsWithoutConsent === 'true', disabled: disabled, "aria-labelledby": "sso-admits-without-consent-label", onClick: () => onChange('ssoAdmitsWithoutConsent', draft.ssoAdmitsWithoutConsent === 'true' ? '' : 'true') }), _jsx(Text, { id: "sso-admits-without-consent-label", sx: { fontSize: 1 }, children: "Skip it for clients you already admit" })] }), _jsxs(FormControl.Caption, { children: ["Only for somebody who signed in through one of", ' ', _jsx("strong", { children: "your own" }), " enabled identity providers, and only for a client named in ", _jsx("strong", { children: "Admitted clients" }), " above \u2014 which must not be empty. Every other session still sees the screen. This does not require anyone to sign in through your directory; it only says the screen adds nothing when they did."] }), _jsx(Note, { children: notes.ssoAdmitsWithoutConsent })] })] })] }));
|
|
113
105
|
export default PolicyForm;
|
|
@@ -56,7 +56,7 @@ export const PolicyHistory = ({ orgUid, subjectUid, limit = 20, }) => {
|
|
|
56
56
|
.slice(0, limit);
|
|
57
57
|
}, [events.data, removals.data, subjectUid, limit]);
|
|
58
58
|
if ((events.isPending || removals.isPending) && rows.length === 0) {
|
|
59
|
-
return (_jsx(Box, {
|
|
59
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", py: 3, children: _jsx(Spinner, { size: "small" }) }));
|
|
60
60
|
}
|
|
61
61
|
if (events.isError || removals.isError) {
|
|
62
62
|
return (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "The history could not be read. The policy above is unaffected." }));
|
|
@@ -64,12 +64,6 @@ export const PolicyHistory = ({ orgUid, subjectUid, limit = 20, }) => {
|
|
|
64
64
|
if (rows.length === 0) {
|
|
65
65
|
return (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: "No recorded change. Either this layer has not been edited, or this deployment routes identity records to a log pipeline rather than to the audit \u2014 an administrator can tell you which." }));
|
|
66
66
|
}
|
|
67
|
-
return (_jsx(Box, {
|
|
68
|
-
display: 'flex',
|
|
69
|
-
gap: 2,
|
|
70
|
-
alignItems: 'baseline',
|
|
71
|
-
flexWrap: 'wrap',
|
|
72
|
-
fontSize: 0,
|
|
73
|
-
}, children: [_jsx(Text, { sx: { color: 'fg.muted', whiteSpace: 'nowrap' }, children: _jsx(RelativeTime, { datetime: event.at }) }), _jsx(Text, { sx: { fontWeight: 'semibold' }, children: event.userUid || 'unknown' }), actedThrough(event) && (_jsxs(Label, { size: "small", variant: "attention", children: ["through ", actedThrough(event)] })), _jsx(Text, { children: describeChange(event) }), String(event.clientInfo?.version ?? '') && (_jsxs(Label, { size: "small", variant: "secondary", children: ["v", String(event.clientInfo?.version ?? '')] }))] }, event.uid))) }));
|
|
67
|
+
return (_jsx(Box, { display: "grid", gap: 2, children: rows.map(event => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "baseline", flexWrap: "wrap", fontSize: 0, children: [_jsx(Text, { sx: { color: 'fg.muted', whiteSpace: 'nowrap' }, children: _jsx(RelativeTime, { datetime: event.at }) }), _jsx(Text, { sx: { fontWeight: 'semibold' }, children: event.userUid || 'unknown' }), actedThrough(event) && (_jsxs(Label, { size: "small", variant: "attention", children: ["through ", actedThrough(event)] })), _jsx(Text, { children: describeChange(event) }), String(event.clientInfo?.version ?? '') && (_jsxs(Label, { size: "small", variant: "secondary", children: ["v", String(event.clientInfo?.version ?? '')] }))] }, event.uid))) }));
|
|
74
68
|
};
|
|
75
69
|
export default PolicyHistory;
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* @module views/mcp/RunDetail
|
|
27
27
|
*/
|
|
28
28
|
import type { JSX } from 'react';
|
|
29
|
-
import type
|
|
29
|
+
import { type McpTask, type McpTaskStatus } from '../../models/McpTask';
|
|
30
30
|
import type { McpSpanNode } from '../../api/mcp/observability';
|
|
31
31
|
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
32
32
|
/** How a run's status is drawn. Shared with the runs list. */
|
|
@@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom';
|
|
|
6
6
|
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
7
7
|
import { useAnswerTask, useCancelTask, useRunTrace, useTask, useTaskEvents, } from '../../hooks/useMcp.js';
|
|
8
8
|
import { TraceTimelineSection } from './TraceTimeline.js';
|
|
9
|
+
import { taskFailureOf, } from '../../models/McpTask.js';
|
|
9
10
|
/** How a run's status is drawn. Shared with the runs list. */
|
|
10
11
|
export const RUN_STATUS_LOOK = {
|
|
11
12
|
working: { label: 'Working', variant: 'success' },
|
|
@@ -148,39 +149,19 @@ export const RunDetail = ({ taskUid, errorState, routes, mock = false, }) => {
|
|
|
148
149
|
return (_jsx(McpErrorBlankslate, { state: errorState(run.error, 'Run'), onRetry: () => run.refetch() }));
|
|
149
150
|
}
|
|
150
151
|
if (!mock && (run.isLoading || !run.data)) {
|
|
151
|
-
return (_jsx(Box, {
|
|
152
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", p: 4, children: _jsx(Spinner, {}) }));
|
|
152
153
|
}
|
|
153
154
|
const task = mock ? SAMPLE_RUN : run.data;
|
|
155
|
+
// An empty object is no failure: the server sends `{}` while nothing failed.
|
|
156
|
+
const failure = taskFailureOf(task.error);
|
|
154
157
|
const look = RUN_STATUS_LOOK[task.status];
|
|
155
158
|
const over = isRunOver(task.status);
|
|
156
159
|
const output = outputTextOf(task);
|
|
157
|
-
return (_jsxs(Box, {
|
|
160
|
+
return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [_jsxs(Box, { children: [_jsx(Link, { href: routes.runs, onClick: event => {
|
|
158
161
|
event.preventDefault();
|
|
159
162
|
navigate(routes.runs);
|
|
160
|
-
}, sx: { fontSize: 0 }, children: "All runs" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, mt: 1, mb: 1 }, children: task.tool || 'Run' }), _jsxs(Box, { sx: {
|
|
161
|
-
display: 'flex',
|
|
162
|
-
gap: 2,
|
|
163
|
-
alignItems: 'center',
|
|
164
|
-
flexWrap: 'wrap',
|
|
165
|
-
}, children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'fg.muted' }, children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["updated ", _jsx(RelativeTime, { datetime: task.lastUpdatedAt })] })] }), task.statusMessage && (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2 }, children: task.statusMessage }))] }), task.status === 'input_required' && (_jsxs(Box, { sx: {
|
|
166
|
-
borderWidth: 1,
|
|
167
|
-
borderStyle: 'solid',
|
|
168
|
-
borderColor: 'attention.emphasis',
|
|
169
|
-
borderRadius: 2,
|
|
170
|
-
p: 3,
|
|
171
|
-
display: 'grid',
|
|
172
|
-
gap: 2,
|
|
173
|
-
}, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "This run is waiting on you" }), _jsx(Textarea, { "aria-label": "Your answer", value: reply, onChange: event => setReply(event.target.value), placeholder: "Your answer", rows: 3 }), _jsx(Box, { children: _jsx(Button, { variant: "primary", disabled: !reply.trim() || answer.isPending, onClick: () => answer.mutate({ taskUid: task.uid, input: { answer: reply } }, { onSuccess: () => setReply('') }), children: answer.isPending ? 'Answering…' : 'Answer' }) })] })), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Output" }), output ? (_jsx(Box, { as: "pre", sx: {
|
|
174
|
-
fontFamily: 'mono',
|
|
175
|
-
fontSize: 0,
|
|
176
|
-
whiteSpace: 'pre-wrap',
|
|
177
|
-
overflowX: 'auto',
|
|
178
|
-
bg: 'canvas.subtle',
|
|
179
|
-
borderRadius: 2,
|
|
180
|
-
p: 3,
|
|
181
|
-
m: 0,
|
|
182
|
-
}, children: output })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: over
|
|
163
|
+
}, sx: { fontSize: 0 }, children: "All runs" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, mt: 1, mb: 1 }, children: task.tool || 'Run' }), _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'fg.muted' }, children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["updated ", _jsx(RelativeTime, { datetime: task.lastUpdatedAt })] })] }), task.statusMessage && (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2 }, children: task.statusMessage }))] }), task.status === 'input_required' && (_jsxs(Box, { borderWidth: 1, borderStyle: "solid", borderColor: "attention.emphasis", borderRadius: 2, p: 3, display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "This run is waiting on you" }), _jsx(Textarea, { "aria-label": "Your answer", value: reply, onChange: event => setReply(event.target.value), placeholder: "Your answer", rows: 3 }), _jsx(Box, { children: _jsx(Button, { variant: "primary", disabled: !reply.trim() || answer.isPending, onClick: () => answer.mutate({ taskUid: task.uid, input: { answer: reply } }, { onSuccess: () => setReply('') }), children: answer.isPending ? 'Answering…' : 'Answer' }) })] })), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Output" }), output ? (_jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 0, whiteSpace: "pre-wrap", overflowX: "auto", bg: "canvas.subtle", borderRadius: 2, p: 3, m: 0, children: output })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: over
|
|
183
164
|
? 'This run printed nothing.'
|
|
184
|
-
: 'Nothing printed yet. Output appears here as it arrives.' }))] }),
|
|
165
|
+
: 'Nothing printed yet. Output appears here as it arrives.' }))] }), failure && (_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Why it failed" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'danger.fg' }, children: failure })] })), _jsx(TraceTimelineSection, { tree: mock ? SAMPLE_TRACE : (trace.data?.tree ?? []), traceId: mock ? SAMPLE_TRACE[0].span.trace_id : trace.data?.traceId, live: !over }), !over && (_jsxs(Box, { children: [_jsx(Button, { variant: "danger", disabled: cancel.isPending, onClick: () => cancel.mutate(task.uid), children: cancel.isPending ? 'Cancelling…' : 'Cancel this run' }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 2 }, children: "Cancelling interrupts the work itself, so the sandbox stops being billed for it. What it has already printed is kept." })] }))] }));
|
|
185
166
|
};
|
|
186
167
|
export default RunDetail;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { ActionList, ActionMenu, Button, Flash, FormControl, Heading, IconButton, Label, Spinner, Text, TextInput, } from '@primer/react';
|
|
4
4
|
import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
|
|
5
|
-
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { Box, Card } from '@datalayer/primer-addons';
|
|
6
6
|
import { KebabHorizontalIcon, PeopleIcon } from '@primer/octicons-react';
|
|
7
7
|
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
8
|
import { useCoreStore } from '../../state/index.js';
|
|
@@ -112,7 +112,7 @@ export const ScimProvisioning = ({ errorState, orgUid, readOnly = false, showTit
|
|
|
112
112
|
header: 'Name',
|
|
113
113
|
field: 'name',
|
|
114
114
|
rowHeader: true,
|
|
115
|
-
renderCell: row => (_jsxs(Box, {
|
|
115
|
+
renderCell: row => (_jsxs(Box, { display: "grid", gap: 1, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: row.name || 'SCIM' }), row.revoked && (_jsx(Label, { variant: "danger", size: "small", children: "Revoked" }))] })),
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
header: 'Last used',
|
|
@@ -142,19 +142,7 @@ export const ScimProvisioning = ({ errorState, orgUid, readOnly = false, showTit
|
|
|
142
142
|
return (_jsx(McpErrorBlankslate, { state: errorState(tokens.error, 'SCIM provisioning'), onRetry: () => tokens.refetch() }));
|
|
143
143
|
}
|
|
144
144
|
const shown = SCIM_STATUS_MESSAGES[status.state];
|
|
145
|
-
return (_jsxs(Box, { sx: { display: '
|
|
146
|
-
display: 'flex',
|
|
147
|
-
alignItems: 'start',
|
|
148
|
-
justifyContent: 'space-between',
|
|
149
|
-
gap: 3,
|
|
150
|
-
}, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Directory provisioning" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Over SCIM. Sign-in can only act on somebody who turns up; this is what removes the person who does not." })] }), !readOnly && (_jsx(Button, { variant: "primary", ref: returnFocusRef, onClick: openCreate, children: "New SCIM token" }))] })), tokens.isPending && !tokens.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Flash, { variant: shown.variant, children: [_jsx(Text, { as: "p", sx: { fontWeight: 'semibold', m: 0 }, children: shown.heading }), _jsx(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0 }, children: shown.body }), status.lastUsedAt && (_jsxs(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0, color: 'fg.muted' }, children: ["Last provisioning call ", timeAgo(status.lastUsedAt), "."] }))] }), rows.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "scim-tokens", children: "SCIM tokens" }), _jsx(Table.Subtitle, { as: "p", id: "scim-tokens-subtitle", children: "A revoked token is kept rather than deleted, so the provisioning it did stays attributable." }), _jsx(DataTable, { "aria-labelledby": "scim-tokens", "aria-describedby": "scim-tokens-subtitle", data: rows, columns: columns })] }), _jsxs(Box, { sx: {
|
|
151
|
-
border: '1px solid',
|
|
152
|
-
borderColor: 'border.default',
|
|
153
|
-
borderRadius: 2,
|
|
154
|
-
p: 3,
|
|
155
|
-
display: 'grid',
|
|
156
|
-
gap: 2,
|
|
157
|
-
}, children: [_jsx(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: "What to give the directory" }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "SCIM base URL" }), _jsx(TextInput, { block: true, readOnly: true, value: scimBaseUrl(iamUrl), "aria-describedby": "scim-base-url-note" }), _jsxs(FormControl.Caption, { id: "scim-base-url-note", children: ["Okta and Entra append ", _jsx("code", { children: "/Users" }), " themselves. Use the token as a bearer credential; the token is what says which organization is being provisioned, so nothing else names this one."] })] }), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: ["Groups are not provisioned over SCIM \u2014 this server answers them ", _jsx("code", { children: "501" }), " saying so. Roles come from the sign-in token\u2019s groups claim and the provider\u2019s role mappings, above; a second source of the same fact would eventually disagree with the first."] })] })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PeopleIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No SCIM token" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Issue one to let this organization\u2019s directory add people when they join and \u2014 the part sign-in cannot do \u2014 remove them when they leave." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: openCreate, children: "New SCIM token" }))] }))] })), creating && (_jsx(Dialog, { title: "New SCIM token", onClose: () => setCreating(false), returnFocusRef: returnFocusRef, footerButtons: [
|
|
145
|
+
return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [showTitle && (_jsxs(Box, { display: "flex", alignItems: "start", justifyContent: "space-between", gap: 3, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Directory provisioning" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Over SCIM. Sign-in can only act on somebody who turns up; this is what removes the person who does not." })] }), !readOnly && (_jsx(Button, { variant: "primary", ref: returnFocusRef, onClick: openCreate, children: "New SCIM token" }))] })), tokens.isPending && !tokens.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Flash, { variant: shown.variant, children: [_jsx(Text, { as: "p", sx: { fontWeight: 'semibold', m: 0 }, children: shown.heading }), _jsx(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0 }, children: shown.body }), status.lastUsedAt && (_jsxs(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0, color: 'fg.muted' }, children: ["Last provisioning call ", timeAgo(status.lastUsedAt), "."] }))] }), rows.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "scim-tokens", children: "SCIM tokens" }), _jsx(Table.Subtitle, { as: "p", id: "scim-tokens-subtitle", children: "A revoked token is kept rather than deleted, so the provisioning it did stays attributable." }), _jsx(DataTable, { "aria-labelledby": "scim-tokens", "aria-describedby": "scim-tokens-subtitle", data: rows, columns: columns })] }), _jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, children: [_jsx(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: "What to give the directory" }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "SCIM base URL" }), _jsx(TextInput, { block: true, readOnly: true, value: scimBaseUrl(iamUrl), "aria-describedby": "scim-base-url-note" }), _jsxs(FormControl.Caption, { id: "scim-base-url-note", children: ["Okta and Entra append ", _jsx("code", { children: "/Users" }), " themselves. Use the token as a bearer credential; the token is what says which organization is being provisioned, so nothing else names this one."] })] }), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: ["Groups are not provisioned over SCIM \u2014 this server answers them ", _jsx("code", { children: "501" }), " saying so. Roles come from the sign-in token\u2019s groups claim and the provider\u2019s role mappings, above; a second source of the same fact would eventually disagree with the first."] })] })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PeopleIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No SCIM token" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Issue one to let this organization\u2019s directory add people when they join and \u2014 the part sign-in cannot do \u2014 remove them when they leave." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: openCreate, children: "New SCIM token" }))] }))] })), creating && (_jsx(Dialog, { title: "New SCIM token", onClose: () => setCreating(false), returnFocusRef: returnFocusRef, footerButtons: [
|
|
158
146
|
{
|
|
159
147
|
buttonType: 'default',
|
|
160
148
|
content: 'Cancel',
|
|
@@ -168,6 +156,6 @@ export const ScimProvisioning = ({ errorState, orgUid, readOnly = false, showTit
|
|
|
168
156
|
},
|
|
169
157
|
], children: _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, autoFocus: true, value: name, placeholder: "Okta", onChange: event => setName(event.target.value) }), _jsx(FormControl.Caption, { children: "Whatever tells this directory from another. Only for your own reading \u2014 it is not part of the credential." })] }) })), secret && (_jsx(Dialog, { title: "Copy this token now", onClose: closeSecret, footerButtons: [
|
|
170
158
|
{ buttonType: 'primary', content: 'Done', onClick: closeSecret },
|
|
171
|
-
], children: _jsxs(Box, {
|
|
159
|
+
], children: _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Flash, { variant: "warning", children: "This is the only time this token is shown. Only a hash of it is stored, so it cannot be shown again \u2014 if it is lost, rotate." }), _jsx(TextInput, { block: true, readOnly: true, value: secret, "aria-label": "SCIM token", sx: { fontFamily: 'mono' }, onFocus: event => event.target.select() }), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: ["Give it to the directory as a bearer token, with the base URL", ' ', _jsx("code", { children: scimBaseUrl(iamUrl) }), "."] })] }) }))] }));
|
|
172
160
|
};
|
|
173
161
|
export default ScimProvisioning;
|
|
@@ -97,7 +97,7 @@ export const ServiceAgents = ({ errorState, orgUid, readOnly = false, showTitle
|
|
|
97
97
|
header: 'Agent',
|
|
98
98
|
field: 'name',
|
|
99
99
|
rowHeader: true,
|
|
100
|
-
renderCell: row => (_jsxs(Box, {
|
|
100
|
+
renderCell: row => (_jsxs(Box, { display: "grid", children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: row.name || row.uid }), row.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.description }))] })),
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
header: 'Kind',
|
|
@@ -134,12 +134,7 @@ export const ServiceAgents = ({ errorState, orgUid, readOnly = false, showTitle
|
|
|
134
134
|
if (agents.isError) {
|
|
135
135
|
return (_jsx(McpErrorBlankslate, { state: errorState(agents.error, 'Service agents'), onRetry: () => agents.refetch() }));
|
|
136
136
|
}
|
|
137
|
-
return (_jsxs(Box, {
|
|
138
|
-
display: 'flex',
|
|
139
|
-
alignItems: 'start',
|
|
140
|
-
justifyContent: 'space-between',
|
|
141
|
-
gap: 3,
|
|
142
|
-
}, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Service Agents" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Agents that belong to this organization rather than to a person: a pipeline, a CI job, a bot. Each holds its own key and spends under its own name." })] }), !readOnly && (_jsx(Button, { variant: "primary", onClick: () => setCreating(true), children: "New service agent" }))] })), agents.isPending && !agents.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : rows.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "service-agents", children: "Service agents" }), _jsx(Table.Subtitle, { as: "p", id: "service-agents-subtitle", children: "A revoked agent stays listed: its audit rows name it, and a uid that resolves to nothing makes them unreadable a year from now." }), _jsx(DataTable, { "aria-labelledby": "service-agents", "aria-describedby": "service-agents-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(KeyIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No service agent" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "An agent that runs on somebody\u2019s grant stops when that person leaves, and its spend shows against them. A service agent is the organization\u2019s own." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: () => setCreating(true), children: "New service agent" }))] })), creating && (_jsx(Dialog, { title: "New service agent", onClose: closeCreate, returnFocusRef: returnFocusRef, footerButtons: [
|
|
137
|
+
return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [showTitle && (_jsxs(Box, { display: "flex", alignItems: "start", justifyContent: "space-between", gap: 3, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Service Agents" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Agents that belong to this organization rather than to a person: a pipeline, a CI job, a bot. Each holds its own key and spends under its own name." })] }), !readOnly && (_jsx(Button, { variant: "primary", onClick: () => setCreating(true), children: "New service agent" }))] })), agents.isPending && !agents.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : rows.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "service-agents", children: "Service agents" }), _jsx(Table.Subtitle, { as: "p", id: "service-agents-subtitle", children: "A revoked agent stays listed: its audit rows name it, and a uid that resolves to nothing makes them unreadable a year from now." }), _jsx(DataTable, { "aria-labelledby": "service-agents", "aria-describedby": "service-agents-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(KeyIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No service agent" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "An agent that runs on somebody\u2019s grant stops when that person leaves, and its spend shows against them. A service agent is the organization\u2019s own." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: () => setCreating(true), children: "New service agent" }))] })), creating && (_jsx(Dialog, { title: "New service agent", onClose: closeCreate, returnFocusRef: returnFocusRef, footerButtons: [
|
|
143
138
|
{ buttonType: 'default', content: 'Cancel', onClick: closeCreate },
|
|
144
139
|
{
|
|
145
140
|
buttonType: 'primary',
|
|
@@ -151,21 +146,13 @@ export const ServiceAgents = ({ errorState, orgUid, readOnly = false, showTitle
|
|
|
151
146
|
// into an obvious gap in the form.
|
|
152
147
|
disabled: create.isPending || !name.trim() || scopes.length === 0,
|
|
153
148
|
},
|
|
154
|
-
], children: _jsxs(Box, {
|
|
149
|
+
], children: _jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: name, onChange: event => setName(event.target.value), placeholder: "nightly ingest" }), _jsx(FormControl.Caption, { children: "What somebody reading an audit row or a spend line a year from now has to go on." })] }), _jsxs(Box, { children: [_jsx(Text, { as: "p", sx: { fontSize: 1, fontWeight: 'semibold', mb: 1 }, children: "Allowed to" }), _jsx(Box, { display: "grid", gap: 1, children: SERVICE_AGENT_SCOPES.map(scope => (_jsxs(FormControl, { children: [_jsx(Checkbox, { checked: scopes.includes(scope), onChange: event => toggleScope(scope, event.target.checked) }), _jsx(FormControl.Label, { children: scope })] }, scope))) }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1 }, children: "The scopes about a person \u2014 their profile, acting as them \u2014 are not here: a pipeline is not a person, and granting one to it would do nothing while looking like it did something." })] })] }) })), keyShown && (_jsx(Dialog, { title: keyShown.rotated ? 'The new key' : 'The key', onClose: () => setKeyShown(null), returnFocusRef: returnFocusRef, footerButtons: [
|
|
155
150
|
{
|
|
156
151
|
buttonType: 'primary',
|
|
157
152
|
content: 'I have stored it',
|
|
158
153
|
onClick: () => setKeyShown(null),
|
|
159
154
|
},
|
|
160
|
-
], children: _jsxs(Box, {
|
|
161
|
-
fontFamily: 'mono',
|
|
162
|
-
fontSize: 1,
|
|
163
|
-
p: 2,
|
|
164
|
-
m: 0,
|
|
165
|
-
bg: 'canvas.subtle',
|
|
166
|
-
borderRadius: 2,
|
|
167
|
-
overflowX: 'auto',
|
|
168
|
-
}, children: keyShown.key }), keyShown.rotated && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [keyShown.agent, "\u2019s previous key stopped working when this one was issued."] }))] }) })), rotating && (_jsx(Dialog, { title: "Rotate this key?", onClose: () => setRotating(null), returnFocusRef: returnFocusRef, footerButtons: [
|
|
155
|
+
], children: _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: "This is shown once and cannot be shown again." }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "We store a hash of it, so there is no page and no support request that can show it to you a second time. If you lose it, rotate \u2014 that issues a new key and stops this one." }), _jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 1, p: 2, m: 0, bg: "canvas.subtle", borderRadius: 2, overflowX: "auto", children: keyShown.key }), keyShown.rotated && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [keyShown.agent, "\u2019s previous key stopped working when this one was issued."] }))] }) })), rotating && (_jsx(Dialog, { title: "Rotate this key?", onClose: () => setRotating(null), returnFocusRef: returnFocusRef, footerButtons: [
|
|
169
156
|
{
|
|
170
157
|
buttonType: 'default',
|
|
171
158
|
content: 'Keep it',
|
|
@@ -53,29 +53,23 @@ const TeamPolicyForm = ({ orgUid, teamUid, teamName, readOnly, }) => {
|
|
|
53
53
|
});
|
|
54
54
|
};
|
|
55
55
|
if (layer.isPending && layer.data === undefined) {
|
|
56
|
-
return (_jsx(Box, {
|
|
56
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", py: 4, children: _jsx(Spinner, {}) }));
|
|
57
57
|
}
|
|
58
|
-
return (_jsxs(Box, {
|
|
58
|
+
return (_jsxs(Box, { display: "grid", gap: 3, children: [conflict && (_jsxs(Flash, { variant: "warning", children: [_jsx(Text, { sx: { fontSize: 1 }, children: conflict }), _jsx(Box, { mt: 2, children: _jsx(Button, { size: "small", onClick: () => {
|
|
59
59
|
setConflict('');
|
|
60
60
|
layer.refetch();
|
|
61
61
|
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, disabled: readOnly, notes: {
|
|
62
62
|
maxConcurrentSandboxes: "Counted over this team's own sandboxes, not the organization's " +
|
|
63
63
|
'— so another team being busy never uses up this limit. The ' +
|
|
64
64
|
"organization's cap still applies on top.",
|
|
65
|
-
}, belowAllowedClients: _jsx(AdmittedClients, { scope: "team", subjectUid: teamUid, edited: draft.allowedClients !== stored.allowedClients }) }), !readOnly && (_jsxs(Box, {
|
|
65
|
+
}, belowAllowedClients: _jsx(AdmittedClients, { scope: "team", subjectUid: teamUid, edited: draft.allowedClients !== stored.allowedClients }) }), !readOnly && (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", children: [_jsxs(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: ["Save ", teamName, "\u2019s policy"] }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { marginLeft: "auto", children: _jsx(Button, { variant: "danger", disabled: remove.isPending, onClick: () => remove.mutate(undefined, {
|
|
66
66
|
onSuccess: () => enqueueToast(`${teamName} narrows nothing of its own.`, {
|
|
67
67
|
variant: 'success',
|
|
68
68
|
}),
|
|
69
69
|
onError: error => enqueueToast(`Could not remove: ${error.message}`, {
|
|
70
70
|
variant: 'error',
|
|
71
71
|
}),
|
|
72
|
-
}), children: "Remove" }) }))] })), !readOnly && _jsx(TeamSessions, { teamUid: teamUid, teamName: teamName }), _jsxs(Box, { sx: {
|
|
73
|
-
borderTop: '1px solid',
|
|
74
|
-
borderColor: 'border.muted',
|
|
75
|
-
pt: 3,
|
|
76
|
-
display: 'grid',
|
|
77
|
-
gap: 2,
|
|
78
|
-
}, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "History" }), _jsx(PolicyHistory, { orgUid: orgUid, subjectUid: teamUid })] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "A team can be stricter than its organization and never looser. Permitting a tool the organization denies does nothing, and a cap above the organization\u2019s leaves the organization\u2019s in force." })] }));
|
|
72
|
+
}), children: "Remove" }) }))] })), !readOnly && _jsx(TeamSessions, { teamUid: teamUid, teamName: teamName }), _jsxs(Box, { borderTop: "1px solid", borderColor: "border.muted", pt: 3, display: "grid", gap: 2, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "History" }), _jsx(PolicyHistory, { orgUid: orgUid, subjectUid: teamUid })] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "A team can be stricter than its organization and never looser. Permitting a tool the organization denies does nothing, and a cap above the organization\u2019s leaves the organization\u2019s in force." })] }));
|
|
79
73
|
};
|
|
80
74
|
/**
|
|
81
75
|
* Ending every session consented in one team.
|
|
@@ -117,13 +111,7 @@ const TeamSessions = ({ teamUid, teamName, }) => {
|
|
|
117
111
|
},
|
|
118
112
|
});
|
|
119
113
|
};
|
|
120
|
-
return (_jsxs(Box, { sx: {
|
|
121
|
-
borderTop: '1px solid',
|
|
122
|
-
borderColor: 'border.muted',
|
|
123
|
-
pt: 3,
|
|
124
|
-
display: 'grid',
|
|
125
|
-
gap: 2,
|
|
126
|
-
}, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "Sessions" }), _jsxs(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: ["Every agent connected while acting for ", teamName, " loses its grant at once. Each asks again the next time somebody connects it, and goes through the approval screen rather than being answered from what was agreed before \u2014 so this is also how a fresh approval is forced."] }), _jsx(Box, { children: _jsxs(Button, { variant: "danger", ref: returnFocusRef, onClick: () => setAsking(true), disabled: disconnect.isPending, children: ["End ", teamName, "\u2019s sessions"] }) }), asking && (_jsxs(Dialog, { title: `End every session in ${teamName}?`, onClose: () => setAsking(false), returnFocusRef: returnFocusRef, footerButtons: [
|
|
114
|
+
return (_jsxs(Box, { borderTop: "1px solid", borderColor: "border.muted", pt: 3, display: "grid", gap: 2, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "Sessions" }), _jsxs(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: ["Every agent connected while acting for ", teamName, " loses its grant at once. Each asks again the next time somebody connects it, and goes through the approval screen rather than being answered from what was agreed before \u2014 so this is also how a fresh approval is forced."] }), _jsx(Box, { children: _jsxs(Button, { variant: "danger", ref: returnFocusRef, onClick: () => setAsking(true), disabled: disconnect.isPending, children: ["End ", teamName, "\u2019s sessions"] }) }), asking && (_jsxs(Dialog, { title: `End every session in ${teamName}?`, onClose: () => setAsking(false), returnFocusRef: returnFocusRef, footerButtons: [
|
|
127
115
|
{
|
|
128
116
|
buttonType: 'default',
|
|
129
117
|
content: 'Keep them',
|
|
@@ -146,12 +134,12 @@ export const TeamPolicies = ({ errorState, orgUid, readOnly = false, showTitle =
|
|
|
146
134
|
return (_jsx(McpErrorBlankslate, { state: errorState(teams.error, "The organization's teams"), onRetry: () => teams.refetch() }));
|
|
147
135
|
}
|
|
148
136
|
if (teams.isPending && !teams.data) {
|
|
149
|
-
return (_jsx(Box, {
|
|
137
|
+
return (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) }));
|
|
150
138
|
}
|
|
151
139
|
if ((teams.data ?? []).length === 0) {
|
|
152
140
|
return (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PeopleIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No teams" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "A team is a scope, not a label: it gets its own rules, its own sandbox limit and its own alerts. Create one in the organization\u2019s settings and it appears here." }) })] }));
|
|
153
141
|
}
|
|
154
|
-
return (_jsxs(Box, {
|
|
142
|
+
return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, maxWidth: "52rem", children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Team policies" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "What each team narrows, on top of this organization\u2019s own rules. A team owner may write their own team\u2019s." })] })), _jsx(Box, { maxWidth: "20rem", children: _jsx(Select, { value: current?.uid ?? '', onChange: event => setChosen(event.target.value), "aria-label": "Team", children: (teams.data ?? []).map(team => (_jsx(Select.Option, { value: team.uid, children: team.name }, team.uid))) }) }), current && (
|
|
155
143
|
// Keyed by the team, so choosing another unmounts the form rather
|
|
156
144
|
// than carrying one team's unsaved draft onto the next.
|
|
157
145
|
_jsx(TeamPolicyForm, { orgUid: orgUid, teamUid: current.uid, teamName: current.name, readOnly: readOnly }, current.uid))] }));
|