@datalayer/core 1.1.66 → 1.2.12
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/README.md +43 -20
- package/lib/api/constants.d.ts +1 -1
- package/lib/api/constants.js +1 -1
- package/lib/api/contents/attachments.js +3 -7
- package/lib/api/contents/datasets.d.ts +48 -0
- package/lib/api/contents/datasets.js +44 -7
- package/lib/api/contents/runtimeMounts.js +1 -1
- package/lib/api/contents/sandboxUid.d.ts +13 -45
- package/lib/api/contents/sandboxUid.js +14 -65
- package/lib/api/evals/client.d.ts +402 -0
- package/lib/api/evals/client.js +359 -0
- package/lib/api/evals/index.d.ts +10 -0
- package/lib/api/evals/index.js +18 -0
- package/lib/api/evals/request.d.ts +43 -0
- package/lib/api/evals/request.js +67 -0
- package/lib/api/evals/status.d.ts +27 -0
- package/lib/api/evals/status.js +122 -0
- package/lib/api/evals/types.d.ts +1335 -0
- package/lib/api/evals/types.js +5 -0
- package/lib/api/iam/authentication.js +6 -4
- package/lib/api/iam/connectedAgents.d.ts +29 -0
- package/lib/api/iam/connectedAgents.js +35 -0
- package/lib/api/iam/healthz.js +2 -2
- package/lib/api/iam/identityProviders.d.ts +76 -0
- package/lib/api/iam/identityProviders.js +187 -0
- package/lib/api/iam/index.d.ts +1 -0
- package/lib/api/iam/index.js +1 -0
- package/lib/api/iam/mcpPolicy.d.ts +102 -1
- package/lib/api/iam/mcpPolicy.js +38 -2
- package/lib/api/iam/oauth2.js +18 -12
- package/lib/api/iam/profile.d.ts +17 -1
- package/lib/api/iam/profile.js +27 -0
- package/lib/api/iam/scimTokens.d.ts +79 -0
- package/lib/api/iam/scimTokens.js +141 -0
- package/lib/api/iam/secrets.js +10 -23
- package/lib/api/iam/trials.d.ts +38 -0
- package/lib/api/iam/trials.js +66 -0
- package/lib/api/index.d.ts +4 -0
- package/lib/api/index.js +4 -0
- package/lib/api/mcp/observability.d.ts +34 -11
- package/lib/api/mcp/observability.js +97 -59
- package/lib/api/orchestration/client.d.ts +53 -0
- package/lib/api/orchestration/client.js +105 -0
- package/lib/api/orchestration/events.d.ts +88 -0
- package/lib/api/orchestration/events.js +245 -0
- package/lib/api/orchestration/generated.d.ts +693 -0
- package/lib/api/orchestration/generated.js +687 -0
- package/lib/api/orchestration/index.d.ts +30 -0
- package/lib/api/orchestration/index.js +34 -0
- package/lib/api/orchestration/lifecycle.d.ts +39 -0
- package/lib/api/orchestration/lifecycle.js +57 -0
- package/lib/api/orchestration/measures.d.ts +55 -0
- package/lib/api/orchestration/measures.js +99 -0
- package/lib/api/otel/dashboards.d.ts +56 -0
- package/lib/api/otel/dashboards.js +50 -0
- package/lib/api/otel/index.d.ts +1 -0
- package/lib/api/otel/index.js +1 -0
- package/lib/api/otel/metrics.d.ts +16 -0
- package/lib/api/otel/metrics.js +15 -0
- package/lib/api/scheduler/client.d.ts +32 -0
- package/lib/api/scheduler/client.js +42 -0
- package/lib/api/scheduler/index.d.ts +8 -0
- package/lib/api/scheduler/index.js +16 -0
- package/lib/api/scheduler/request.d.ts +28 -0
- package/lib/api/scheduler/request.js +50 -0
- package/lib/api/scheduler/types.d.ts +88 -0
- package/lib/api/scheduler/types.js +5 -0
- package/lib/api/spacer/comments.d.ts +97 -0
- package/lib/api/spacer/comments.js +45 -0
- package/lib/api/spacer/index.d.ts +9 -0
- package/lib/api/spacer/index.js +17 -0
- package/lib/api/spacer/notebooks.d.ts +33 -0
- package/lib/api/spacer/notebooks.js +39 -0
- package/lib/api/spacer/request.d.ts +25 -0
- package/lib/api/spacer/request.js +46 -0
- package/lib/api/spacer/spaces.d.ts +68 -0
- package/lib/api/spacer/spaces.js +45 -0
- package/lib/api/utils/validation.d.ts +4 -4
- package/lib/api/utils/validation.js +10 -7
- package/lib/client/auth/strategies.js +1 -1
- package/lib/client/constants.d.ts +1 -0
- package/lib/client/constants.js +1 -0
- package/lib/client/mixins/IAMMixin.js +2 -2
- package/lib/components/animation/AnimatedText.d.ts +1 -44
- package/lib/components/animation/AnimatedText.js +6 -122
- package/lib/components/anonymous/AnonymousKeyExpired.d.ts +56 -0
- package/lib/components/anonymous/AnonymousKeyExpired.js +96 -0
- package/lib/components/anonymous/AnonymousKeyTimer.d.ts +63 -0
- package/lib/components/anonymous/AnonymousKeyTimer.js +140 -0
- package/lib/components/anonymous/index.d.ts +7 -0
- package/lib/components/anonymous/index.js +15 -0
- package/lib/components/avatars/UserAvatar.d.ts +7 -1
- package/lib/components/avatars/UserAvatar.js +15 -1
- package/lib/components/billing/BillingEntitySelect.d.ts +6 -0
- package/lib/components/billing/BillingEntitySelect.js +49 -10
- package/lib/components/billing/eligibility.d.ts +22 -0
- package/lib/components/billing/eligibility.js +27 -0
- package/lib/components/billing/index.d.ts +1 -0
- package/lib/components/billing/index.js +1 -0
- package/lib/components/checkout/StripeCheckout.d.ts +1 -1
- package/lib/components/checkout/StripeCheckout.js +1 -1
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +5 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +3 -1
- package/lib/components/display/DatalayerBox.d.ts +2 -3
- package/lib/components/display/DatalayerBox.js +1 -1
- package/lib/components/display/NavLink.js +3 -2
- package/lib/components/display/VisuallyHidden.d.ts +1 -2
- package/lib/components/labels/StatusLabels.d.ts +2 -10
- package/lib/components/labels/StatusLabels.js +2 -26
- package/lib/components/principal/PrincipalAppearance.d.ts +21 -42
- package/lib/components/principal/PrincipalAppearance.js +232 -174
- package/lib/components/principal/PrincipalAvatar.d.ts +8 -1
- package/lib/components/principal/PrincipalAvatar.js +2 -2
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +8 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
- package/lib/components/screencapture/Screencapture.js +46 -3
- package/lib/components/sharing/ShareAccessComponent.d.ts +45 -4
- package/lib/components/sharing/ShareAccessComponent.js +110 -78
- package/lib/components/sharing/SharingEditor.d.ts +5 -1
- package/lib/components/sharing/SharingEditor.js +9 -4
- package/lib/components/sharing/index.d.ts +1 -0
- package/lib/components/sharing/index.js +1 -0
- package/lib/components/sharing/sandboxSharing.d.ts +26 -0
- package/lib/components/sharing/sandboxSharing.js +47 -0
- package/lib/components/spaces/SpaceDetailsOverlay.d.ts +39 -0
- package/lib/components/spaces/SpaceDetailsOverlay.js +80 -0
- package/lib/components/spaces/SpaceDisplay.d.ts +38 -0
- package/lib/components/spaces/SpaceDisplay.js +92 -0
- package/lib/components/spaces/index.d.ts +4 -0
- package/lib/components/spaces/index.js +7 -0
- package/lib/components/spaces/spaceDisplayModel.d.ts +93 -0
- package/lib/components/spaces/spaceDisplayModel.js +170 -0
- package/lib/config/Configuration.d.ts +4 -4
- package/lib/config/Configuration.js +7 -7
- package/lib/config/index.d.ts +1 -0
- package/lib/config/index.js +1 -0
- package/lib/config/planes.d.ts +45 -0
- package/lib/config/planes.js +53 -0
- package/lib/hooks/cacheConverters.d.ts +0 -7
- package/lib/hooks/cacheConverters.js +0 -25
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useBackdrop.d.ts +4 -5
- package/lib/hooks/useBillingEntityStore.js +3 -0
- package/lib/hooks/useCache.d.ts +197 -44
- package/lib/hooks/useCache.js +1586 -494
- package/lib/hooks/useContents.d.ts +22 -1
- package/lib/hooks/useContents.js +90 -21
- package/lib/hooks/useMcp.d.ts +87 -1
- package/lib/hooks/useMcp.js +248 -13
- package/lib/hooks/useNavigate.d.ts +41 -3
- package/lib/hooks/useNavigate.js +116 -52
- package/lib/hooks/usePrincipalCacheStore.js +3 -0
- package/lib/hooks/usePrincipalStore.js +3 -0
- package/lib/hooks/useSpaceCacheStore.d.ts +50 -0
- package/lib/hooks/useSpaceCacheStore.js +85 -0
- package/lib/hooks/useWindowSize.js +1 -1
- package/lib/models/CreditsDTO.d.ts +6 -5
- package/lib/models/CreditsDTO.js +7 -5
- package/lib/models/IAMProvidersSpecs.d.ts +10 -5
- package/lib/models/IAMProvidersSpecs.js +0 -23
- package/lib/models/McpBinding.d.ts +6 -0
- package/lib/models/Organization.d.ts +69 -0
- package/lib/models/Organization.js +96 -0
- package/lib/models/Secret.d.ts +1 -2
- package/lib/models/Secret.js +2 -18
- package/lib/models/StartedBy.d.ts +34 -0
- package/lib/models/StartedBy.js +36 -0
- package/lib/models/Team.d.ts +2 -0
- package/lib/models/Team.js +2 -0
- package/lib/models/User.d.ts +4 -0
- package/lib/models/User.js +3 -0
- package/lib/models/UserSettings.d.ts +13 -0
- package/lib/models/UserSettings.js +9 -0
- package/lib/models/contents/__fixtures__/v1-contracts.json +158 -30
- package/lib/navigation/adapters/react-router.d.ts +1 -1
- package/lib/navigation/adapters/react-router.js +3 -0
- package/lib/navigation/components.js +2 -3
- package/lib/routes/publicPaths.js +4 -0
- package/lib/state/index.d.ts +1 -0
- package/lib/state/index.js +1 -0
- package/lib/state/sessionEnd.d.ts +55 -0
- package/lib/state/sessionEnd.js +162 -0
- package/lib/state/substates/CoreState.js +3 -16
- package/lib/state/substates/IAMState.d.ts +3 -3
- package/lib/state/substates/IAMState.js +7 -0
- package/lib/state/substates/LayoutState.js +12 -0
- package/lib/state/substates/NavigationState.d.ts +82 -0
- package/lib/state/substates/NavigationState.js +255 -0
- package/lib/state/substates/ProfileState.d.ts +1 -0
- package/lib/state/substates/ProfileState.js +1 -0
- package/lib/state/substates/index.d.ts +1 -0
- package/lib/state/substates/index.js +1 -0
- package/lib/utils/Lazy.d.ts +20 -0
- package/lib/utils/Lazy.js +32 -6
- package/lib/utils/Screencapture.d.ts +15 -0
- package/lib/utils/Screencapture.js +26 -20
- package/lib/utils/WithSuspense.d.ts +11 -1
- package/lib/utils/WithSuspense.js +11 -1
- package/lib/views/mcp/AdmittedClients.d.ts +44 -0
- package/lib/views/mcp/AdmittedClients.js +59 -0
- package/lib/views/mcp/ApprovalQueue.d.ts +66 -0
- package/lib/views/mcp/ApprovalQueue.js +95 -0
- package/lib/views/mcp/ConnectedAgents.js +38 -3
- package/lib/views/mcp/EnterpriseConsole.d.ts +1 -1
- package/lib/views/mcp/EnterpriseConsole.js +23 -1
- package/lib/views/mcp/IdentityProviders.d.ts +33 -0
- package/lib/views/mcp/IdentityProviders.js +299 -0
- package/lib/views/mcp/McpDashboard.js +90 -7
- package/lib/views/mcp/McpHome.js +1 -1
- package/lib/views/mcp/McpObservability.d.ts +30 -1
- package/lib/views/mcp/McpObservability.js +105 -19
- package/lib/views/mcp/NotebookRuns.d.ts +39 -0
- package/lib/views/mcp/NotebookRuns.js +44 -0
- package/lib/views/mcp/OrganizationPolicy.js +2 -1
- package/lib/views/mcp/PersonalPolicy.js +2 -1
- package/lib/views/mcp/PolicyForm.d.ts +17 -1
- package/lib/views/mcp/PolicyForm.js +25 -3
- package/lib/views/mcp/PolicyHistory.d.ts +13 -0
- package/lib/views/mcp/PolicyHistory.js +14 -1
- package/lib/views/mcp/RunDetail.d.ts +87 -0
- package/lib/views/mcp/RunDetail.js +186 -0
- package/lib/views/mcp/ScimProvisioning.d.ts +54 -0
- package/lib/views/mcp/ScimProvisioning.js +173 -0
- package/lib/views/mcp/TeamPolicies.js +66 -5
- package/lib/views/mcp/ToolAccess.d.ts +60 -0
- package/lib/views/mcp/ToolAccess.js +101 -0
- package/lib/views/mcp/TraceTimeline.d.ts +81 -0
- package/lib/views/mcp/TraceTimeline.js +132 -0
- package/lib/views/mcp/index.d.ts +5 -0
- package/lib/views/mcp/index.js +5 -0
- package/lib/views/otel/simpleAuthStore.js +3 -0
- package/lib/views/secrets/SecretEdit.js +3 -2
- package/lib/views/secrets/SecretNew.js +5 -1
- package/package.json +271 -264
- package/scripts/generate-mcp-types.py +16 -10
- package/scripts/generate-orchestration-types.py +920 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { ActionList, ActionMenu, Button, Flash, FormControl, Heading, IconButton, Label, Select, Spinner, Text, TextInput, ToggleSwitch, } from '@primer/react';
|
|
4
|
+
import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { KebabHorizontalIcon, KeyIcon, TrashIcon, } from '@primer/octicons-react';
|
|
7
|
+
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
|
+
import { useCreateIdentityProvider, useDeleteIdentityProvider, useDomainVerification, useIdentityProviders, useSetIdentityProviderEnabled, useUpdateIdentityProvider, useVerifyDomain, } from '../../hooks/useMcp.js';
|
|
9
|
+
import { useToast } from '../../hooks/index.js';
|
|
10
|
+
import { IdentityProviderInvalid, ORGANIZATION_ROLES, TEAM_ROLES, } from '../../api/iam/identityProviders.js';
|
|
11
|
+
const ROLE_LABELS = {
|
|
12
|
+
organization_owner: 'Organization owner',
|
|
13
|
+
organization_security_auditor: 'Organization security auditor',
|
|
14
|
+
organization_user_reviewer: 'Organization usage reviewer',
|
|
15
|
+
organization_member: 'Organization member',
|
|
16
|
+
team_owner: 'Team owner',
|
|
17
|
+
team_member: 'Team member',
|
|
18
|
+
};
|
|
19
|
+
const roleLabel = (role) => ROLE_LABELS[role] ?? role;
|
|
20
|
+
const isTeamRole = (role) => TEAM_ROLES.includes(role);
|
|
21
|
+
const BLANK = {
|
|
22
|
+
name: '',
|
|
23
|
+
protocol: 'oidc',
|
|
24
|
+
issuer: '',
|
|
25
|
+
clientId: '',
|
|
26
|
+
clientSecretRef: '',
|
|
27
|
+
domains: [],
|
|
28
|
+
groupClaim: 'groups',
|
|
29
|
+
allowEmailLinking: false,
|
|
30
|
+
roleMappings: [],
|
|
31
|
+
enabled: true,
|
|
32
|
+
};
|
|
33
|
+
const domainsToText = (domains) => domains.join(', ');
|
|
34
|
+
const textToDomains = (text) => Array.from(new Set(text
|
|
35
|
+
.split(/[,\s]+/)
|
|
36
|
+
.map(domain => domain.trim().toLowerCase())
|
|
37
|
+
.filter(Boolean)));
|
|
38
|
+
export const IdentityProviders = ({ errorState, orgUid, readOnly = false, showTitle = true, }) => {
|
|
39
|
+
const { enqueueToast } = useToast();
|
|
40
|
+
const providers = useIdentityProviders(orgUid);
|
|
41
|
+
const create = useCreateIdentityProvider(orgUid);
|
|
42
|
+
const update = useUpdateIdentityProvider(orgUid);
|
|
43
|
+
const setEnabled = useSetIdentityProviderEnabled(orgUid);
|
|
44
|
+
const remove = useDeleteIdentityProvider(orgUid);
|
|
45
|
+
const domainVerification = useDomainVerification(orgUid);
|
|
46
|
+
const verifyDomain = useVerifyDomain(orgUid);
|
|
47
|
+
const [editing, setEditing] = useState(null);
|
|
48
|
+
const [creating, setCreating] = useState(false);
|
|
49
|
+
const [draft, setDraft] = useState(BLANK);
|
|
50
|
+
const [domainsText, setDomainsText] = useState('');
|
|
51
|
+
const [refusal, setRefusal] = useState('');
|
|
52
|
+
const [removing, setRemoving] = useState(null);
|
|
53
|
+
const [managingDomains, setManagingDomains] = useState(null);
|
|
54
|
+
// Primer 37 types Dialog's focus refs with React 18's non-nullable
|
|
55
|
+
// `RefObject<HTMLElement>`. React 19's `useRef(null)` is nullable, and this
|
|
56
|
+
// ref is only handed to Dialog, so narrow it once here.
|
|
57
|
+
const returnFocusRef = useRef(null);
|
|
58
|
+
const rows = useMemo(() => (providers.data ?? []).map(provider => ({
|
|
59
|
+
...provider,
|
|
60
|
+
id: provider.uid,
|
|
61
|
+
})), [providers.data]);
|
|
62
|
+
const openCreate = () => {
|
|
63
|
+
setDraft(BLANK);
|
|
64
|
+
setDomainsText('');
|
|
65
|
+
setRefusal('');
|
|
66
|
+
setCreating(true);
|
|
67
|
+
};
|
|
68
|
+
const openEdit = (provider) => {
|
|
69
|
+
const { uid: _uid, orgUid: _org, verifiedDomains: _verified, createdBy: _by, createdAt: _c, updatedAt: _u, version: _v, ...rest } = provider;
|
|
70
|
+
setDraft(rest);
|
|
71
|
+
setDomainsText(domainsToText(provider.domains));
|
|
72
|
+
setRefusal('');
|
|
73
|
+
setEditing(provider);
|
|
74
|
+
};
|
|
75
|
+
const close = () => {
|
|
76
|
+
setCreating(false);
|
|
77
|
+
setEditing(null);
|
|
78
|
+
setRefusal('');
|
|
79
|
+
};
|
|
80
|
+
const onError = (error) => {
|
|
81
|
+
if (error instanceof IdentityProviderInvalid) {
|
|
82
|
+
// The registry's own words, beside the form: it names what is wrong,
|
|
83
|
+
// and the person can still fix it without guessing.
|
|
84
|
+
setRefusal(error.message);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
enqueueToast(`Could not save: ${error.message}`, { variant: 'error' });
|
|
88
|
+
};
|
|
89
|
+
const apply = () => {
|
|
90
|
+
setRefusal('');
|
|
91
|
+
const domains = textToDomains(domainsText);
|
|
92
|
+
if (domains.length === 0) {
|
|
93
|
+
setRefusal('At least one email domain is needed; a provider no domain routes ' +
|
|
94
|
+
'to is one nobody can sign in through.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const payload = { ...draft, domains };
|
|
98
|
+
if (editing) {
|
|
99
|
+
update.mutate({ uid: editing.uid, provider: payload }, {
|
|
100
|
+
onSuccess: () => {
|
|
101
|
+
close();
|
|
102
|
+
enqueueToast('Identity provider saved.', { variant: 'success' });
|
|
103
|
+
},
|
|
104
|
+
onError,
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
create.mutate(payload, {
|
|
109
|
+
onSuccess: () => {
|
|
110
|
+
close();
|
|
111
|
+
enqueueToast('Identity provider registered. Verify a domain before anybody ' +
|
|
112
|
+
'signs in through it.', { variant: 'success' });
|
|
113
|
+
},
|
|
114
|
+
onError,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
/** Switching a provider off leaves its configuration here; only sign-in
|
|
118
|
+
* through it stops. */
|
|
119
|
+
const toggle = (provider, enabled) => {
|
|
120
|
+
setEnabled.mutate({ uid: provider.uid, enabled }, {
|
|
121
|
+
onError: error => enqueueToast(`Could not change the provider: ${error.message}`, {
|
|
122
|
+
variant: 'error',
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const confirmRemove = () => {
|
|
127
|
+
const provider = removing;
|
|
128
|
+
if (!provider) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
remove.mutate(provider.uid, {
|
|
132
|
+
onSuccess: () => {
|
|
133
|
+
setRemoving(null);
|
|
134
|
+
enqueueToast('Identity provider removed.', { variant: 'success' });
|
|
135
|
+
},
|
|
136
|
+
onError: error => {
|
|
137
|
+
setRemoving(null);
|
|
138
|
+
enqueueToast(`Could not remove: ${error.message}`, {
|
|
139
|
+
variant: 'error',
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
const set = (key, value) => setDraft(current => ({ ...current, [key]: value }));
|
|
145
|
+
const setMapping = (index, patch) => {
|
|
146
|
+
setDraft(current => ({
|
|
147
|
+
...current,
|
|
148
|
+
roleMappings: current.roleMappings.map((mapping, i) => i === index ? { ...mapping, ...patch } : mapping),
|
|
149
|
+
}));
|
|
150
|
+
};
|
|
151
|
+
const addMapping = () => setDraft(current => ({
|
|
152
|
+
...current,
|
|
153
|
+
roleMappings: [
|
|
154
|
+
...current.roleMappings,
|
|
155
|
+
{ group: '', role: 'organization_member', teamUid: '' },
|
|
156
|
+
],
|
|
157
|
+
}));
|
|
158
|
+
const removeMapping = (index) => setDraft(current => ({
|
|
159
|
+
...current,
|
|
160
|
+
roleMappings: current.roleMappings.filter((_, i) => i !== index),
|
|
161
|
+
}));
|
|
162
|
+
const columns = [
|
|
163
|
+
{
|
|
164
|
+
header: 'Provider',
|
|
165
|
+
id: 'name',
|
|
166
|
+
rowHeader: true,
|
|
167
|
+
width: 'growCollapse',
|
|
168
|
+
renderCell: row => (_jsxs(Box, { sx: { display: 'grid' }, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold' }, children: row.name }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.issuer })] })),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
header: 'Domains',
|
|
172
|
+
id: 'domains',
|
|
173
|
+
width: 'growCollapse',
|
|
174
|
+
renderCell: row => (_jsxs(Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 1 }, children: [row.domains.length === 0 && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "none" })), row.domains.map(domain => (_jsxs(Label, { size: "small", variant: row.verifiedDomains.includes(domain) ? 'success' : 'secondary', children: [domain, row.verifiedDomains.includes(domain)
|
|
175
|
+
? ' · verified'
|
|
176
|
+
: ' · claimed'] }, domain)))] })),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
header: 'On',
|
|
180
|
+
id: 'enabled',
|
|
181
|
+
width: '90px',
|
|
182
|
+
renderCell: row => (_jsxs(_Fragment, { children: [_jsx(Text, { id: `idp-toggle-${row.uid}`, sx: { display: 'none' }, children: `Sign-in through ${row.name}` }), _jsx(ToggleSwitch, { size: "small", checked: row.enabled, disabled: readOnly || setEnabled.isPending, "aria-labelledby": `idp-toggle-${row.uid}`, onClick: () => toggle(row, !row.enabled) })] })),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
header: '',
|
|
186
|
+
id: 'actions',
|
|
187
|
+
width: '48px',
|
|
188
|
+
align: 'end',
|
|
189
|
+
renderCell: row => readOnly ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "\u2014" })) : (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Button, { variant: "invisible", size: "small", "aria-label": `Actions for ${row.name}`, icon: KebabHorizontalIcon }) }), _jsx(ActionMenu.Overlay, { align: "end", children: _jsxs(ActionList, { children: [_jsx(ActionList.Item, { onSelect: () => openEdit(row), children: "Edit" }), _jsx(ActionList.Item, { onSelect: () => setManagingDomains(row), disabled: row.domains.length === 0, children: "Manage domains" }), _jsx(ActionList.Item, { variant: "danger", onSelect: () => setRemoving(row), children: "Remove" })] }) })] })),
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
if (providers.isError) {
|
|
193
|
+
return (_jsx(McpErrorBlankslate, { state: errorState(providers.error, 'Identity providers'), onRetry: () => providers.refetch() }));
|
|
194
|
+
}
|
|
195
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { sx: {
|
|
196
|
+
display: 'flex',
|
|
197
|
+
alignItems: 'start',
|
|
198
|
+
justifyContent: 'space-between',
|
|
199
|
+
gap: 3,
|
|
200
|
+
}, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Identity providers" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "The directory this organization signs in through. A domain is claimed the moment it is typed here and routes nothing until it is verified." })] }), !readOnly && (_jsx(Button, { variant: "primary", onClick: openCreate, children: "New provider" }))] })), providers.isPending && !providers.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: "identity-providers", children: "Providers" }), _jsx(Table.Subtitle, { as: "p", id: "identity-providers-subtitle", children: "A provider switched off keeps its configuration and its verified domains; only sign-in through it stops." }), _jsx(DataTable, { "aria-labelledby": "identity-providers", "aria-describedby": "identity-providers-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 directory registered" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "People here sign in the ordinary way. Register a provider to let them sign in through the directory this organization already has." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: openCreate, children: "New provider" }))] })), (creating || editing) && (_jsx(Dialog, { title: editing ? 'Edit identity provider' : 'New identity provider', onClose: close, returnFocusRef: returnFocusRef, footerButtons: [
|
|
201
|
+
{ buttonType: 'default', content: 'Cancel', onClick: close },
|
|
202
|
+
{
|
|
203
|
+
buttonType: 'primary',
|
|
204
|
+
content: editing ? 'Save' : 'Register',
|
|
205
|
+
onClick: apply,
|
|
206
|
+
disabled: create.isPending || update.isPending,
|
|
207
|
+
},
|
|
208
|
+
], children: _jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: draft.name, onChange: event => set('name', event.target.value), placeholder: "Acme Okta" }), _jsx(FormControl.Caption, { children: "What an administrator sees in this list." })] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Issuer" }), _jsx(TextInput, { block: true, value: draft.issuer, onChange: event => set('issuer', event.target.value), placeholder: "https://acme.okta.com" }), _jsx(FormControl.Caption, { children: "Must be https \u2014 discovery, the signing keys and the sign-in redirect all travel over it." })] }), _jsxs(Box, { sx: { display: 'grid', gap: 2, gridTemplateColumns: '1fr 1fr' }, children: [_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Client ID" }), _jsx(TextInput, { block: true, value: draft.clientId, onChange: event => set('clientId', event.target.value) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Client secret" }), _jsx(TextInput, { block: true, value: draft.clientSecretRef, onChange: event => set('clientSecretRef', event.target.value), placeholder: "the secret's name, not the secret" }), _jsx(FormControl.Caption, { children: "The name of a secret already in this organization\u2019s store \u2014 never the secret itself." })] })] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Email domains" }), _jsx(TextInput, { block: true, value: domainsText, onChange: event => setDomainsText(event.target.value), placeholder: "acme.com, acme.io" }), _jsx(FormControl.Caption, { children: "Comma-separated. Claiming a domain is free and routes nothing \u2014 verify it below once it is registered before anybody can sign in through it." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Group claim" }), _jsx(TextInput, { block: true, value: draft.groupClaim, onChange: event => set('groupClaim', event.target.value), placeholder: "groups" }), _jsx(FormControl.Caption, { children: "Which claim in the token carries the groups the mapping below reads." })] }), _jsxs(FormControl, { children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(ToggleSwitch, { size: "small", checked: draft.allowEmailLinking, "aria-labelledby": "allow-email-linking-label", onClick: () => set('allowEmailLinking', !draft.allowEmailLinking) }), _jsx(FormControl.Label, { id: "allow-email-linking-label", children: "Adopt an existing account on a verified address" })] }), _jsx(FormControl.Caption, { children: "Off by default: a sign-in whose address already has a Datalayer account is refused rather than signed in as that account, and the person is told to connect the two from a session they already hold. Turn this on only if the smoother rollout is worth it \u2014 the address still has to be on a verified domain, and the provider still has to say it verified it itself." })] }), _jsxs(Box, { children: [_jsxs(Box, { sx: {
|
|
209
|
+
display: 'flex',
|
|
210
|
+
alignItems: 'center',
|
|
211
|
+
justifyContent: 'space-between',
|
|
212
|
+
mb: 2,
|
|
213
|
+
}, children: [_jsx(FormControl.Label, { children: "Group \u2192 role mapping" }), _jsx(Button, { size: "small", onClick: addMapping, children: "Add mapping" })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 0, mb: 2 }, children: "Granted the next time somebody in the group signs in, and never taken back by a sign-in that omits it \u2014 a slow directory sync should not read as a demotion. Reaches no further than this organization and its own teams." }), draft.roleMappings.length === 0 && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "No mapping: everybody who signs in through this provider becomes an organization member and nothing more." })), _jsx(Box, { sx: { display: 'grid', gap: 2 }, children: draft.roleMappings.map((mapping, index) => (_jsxs(Box, { sx: {
|
|
214
|
+
display: 'grid',
|
|
215
|
+
gap: 2,
|
|
216
|
+
gridTemplateColumns: isTeamRole(mapping.role)
|
|
217
|
+
? '1fr 1fr 1fr auto'
|
|
218
|
+
: '1fr 1fr auto',
|
|
219
|
+
alignItems: 'end',
|
|
220
|
+
}, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Group" }), _jsx(TextInput, { block: true, value: mapping.group, onChange: event => setMapping(index, { group: event.target.value }), placeholder: "admins" })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Grants" }), _jsxs(Select, { value: mapping.role, onChange: event => setMapping(index, {
|
|
221
|
+
role: event.target.value,
|
|
222
|
+
}), children: [ORGANIZATION_ROLES.map(role => (_jsx(Select.Option, { value: role, children: roleLabel(role) }, role))), TEAM_ROLES.map(role => (_jsx(Select.Option, { value: role, children: roleLabel(role) }, role)))] })] }), isTeamRole(mapping.role) && (_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Team" }), _jsx(TextInput, { block: true, value: mapping.teamUid, onChange: event => setMapping(index, { teamUid: event.target.value }), placeholder: "team uid" })] })), _jsx(IconButton, { icon: TrashIcon, variant: "danger", size: "small", "aria-label": "Remove this mapping", onClick: () => removeMapping(index) })] }, index))) })] })] }) })), managingDomains && (_jsx(DomainVerificationDialog, { provider: managingDomains, orgUid: orgUid, onClose: () => setManagingDomains(null), fetchVerification: domainVerification, verify: verifyDomain })), removing && (_jsx(Dialog, { title: "Remove this identity provider?", onClose: () => setRemoving(null), returnFocusRef: returnFocusRef, footerButtons: [
|
|
223
|
+
{
|
|
224
|
+
buttonType: 'default',
|
|
225
|
+
content: 'Keep it',
|
|
226
|
+
onClick: () => setRemoving(null),
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
buttonType: 'danger',
|
|
230
|
+
content: 'Remove',
|
|
231
|
+
onClick: confirmRemove,
|
|
232
|
+
disabled: remove.isPending,
|
|
233
|
+
},
|
|
234
|
+
], children: _jsx(Text, { sx: { fontSize: 1 }, children: "Sign-in through it stops immediately and its domains stop being claimed. If people have used it before, consider turning it off instead \u2014 that keeps its uid resolvable for the sign-in rows that name it." }) }))] }));
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Proving one domain: what to publish, and the button that checks it.
|
|
238
|
+
*
|
|
239
|
+
* Kept as its own dialog rather than inline in the edit form, because
|
|
240
|
+
* proving a domain is not part of editing the provider's configuration —
|
|
241
|
+
* it is a separate act, done once DNS has actually been changed, and
|
|
242
|
+
* conflating the two would make "Save" look like it also verifies.
|
|
243
|
+
*/
|
|
244
|
+
const DomainVerificationDialog = ({ provider, onClose, fetchVerification, verify, }) => {
|
|
245
|
+
const { enqueueToast } = useToast();
|
|
246
|
+
const [selected, setSelected] = useState(provider.domains[0] ?? '');
|
|
247
|
+
const [record, setRecord] = useState(null);
|
|
248
|
+
const [verified, setVerified] = useState(provider.verifiedDomains.includes(selected));
|
|
249
|
+
const [refusal, setRefusal] = useState('');
|
|
250
|
+
const returnFocusRef = useRef(null);
|
|
251
|
+
const load = (domain) => {
|
|
252
|
+
setSelected(domain);
|
|
253
|
+
setRecord(null);
|
|
254
|
+
setRefusal('');
|
|
255
|
+
fetchVerification.mutate({ uid: provider.uid, domain }, {
|
|
256
|
+
onSuccess: result => {
|
|
257
|
+
setRecord(result.verification);
|
|
258
|
+
setVerified(result.verified);
|
|
259
|
+
},
|
|
260
|
+
onError: error => setRefusal(error.message),
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
const runVerify = () => {
|
|
264
|
+
setRefusal('');
|
|
265
|
+
verify.mutate({ uid: provider.uid, domain: selected }, {
|
|
266
|
+
onSuccess: updated => {
|
|
267
|
+
const done = updated.verifiedDomains.includes(selected);
|
|
268
|
+
setVerified(done);
|
|
269
|
+
if (done) {
|
|
270
|
+
enqueueToast(`${selected} is verified and now routes sign-in.`, {
|
|
271
|
+
variant: 'success',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
onError: error => {
|
|
276
|
+
if (error instanceof IdentityProviderInvalid) {
|
|
277
|
+
// "No TXT record was found" and the like — the exact reason DNS
|
|
278
|
+
// did not check out, worth showing rather than a generic failure.
|
|
279
|
+
setRefusal(error.message);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
setRefusal(error.message);
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
return (_jsx(Dialog, { title: `Verify a domain for ${provider.name}`, onClose: onClose, returnFocusRef: returnFocusRef, footerButtons: [
|
|
287
|
+
{ buttonType: 'default', content: 'Close', onClick: onClose },
|
|
288
|
+
], children: _jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: "Publish the record below at your DNS provider, then check it here. Until it checks out, this domain routes no sign-in \u2014 a claim is not proof." }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Domain" }), _jsx(Select, { value: selected, onChange: event => load(event.target.value), children: provider.domains.map(domain => (_jsxs(Select.Option, { value: domain, children: [domain, provider.verifiedDomains.includes(domain) ? ' — verified' : ''] }, domain))) })] }), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), fetchVerification.isPending ? (_jsx(Spinner, { size: "small" })) : record ? (_jsxs(Box, { sx: {
|
|
289
|
+
display: 'grid',
|
|
290
|
+
gap: 1,
|
|
291
|
+
p: 2,
|
|
292
|
+
border: '1px solid',
|
|
293
|
+
borderColor: 'border.default',
|
|
294
|
+
borderRadius: 2,
|
|
295
|
+
fontFamily: 'mono',
|
|
296
|
+
fontSize: 0,
|
|
297
|
+
}, children: [_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Type: ", record.type] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Host: ", record.host] }), _jsxs(Text, { sx: { fontSize: 0, wordBreak: 'break-all' }, children: ["Value: ", record.value] })] })) : (_jsx(Button, { size: "small", onClick: () => load(selected), children: "Show what to publish" })), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Label, { variant: verified ? 'success' : 'secondary', children: verified ? 'Verified — routes sign-in' : 'Not yet verified' }), !verified && (_jsx(Button, { variant: "primary", size: "small", onClick: runVerify, disabled: verify.isPending || !selected, children: verify.isPending ? 'Checking…' : 'Check DNS now' }))] })] }) }));
|
|
298
|
+
};
|
|
299
|
+
export default IdentityProviders;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { ActionList, ActionMenu, Button, Heading, Label, Link, Select, Spinner, Text, } from '@primer/react';
|
|
3
|
+
import { ActionList, ActionMenu, Button, Heading, Label, Link, RelativeTime, Select, Spinner, Text, } from '@primer/react';
|
|
4
4
|
import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
|
|
5
5
|
import { Box } from '@datalayer/primer-addons';
|
|
6
6
|
import { ChecklistIcon, KebabHorizontalIcon, PlayIcon, PlugIcon, StackIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
7
|
+
import { ShareAccessDialog, sandboxSharingUrl } from '../../components/sharing/index.js';
|
|
7
8
|
import { ClientBadge, McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
9
|
import { useDisconnectAgent, useMcpActivity, useTerminateBinding, } from '../../hooks/useMcp.js';
|
|
9
10
|
import { useNavigate, useToast } from '../../hooks/index.js';
|
|
11
|
+
import { useCoreStore } from '../../state/index.js';
|
|
10
12
|
import { clientStatusOf, durationLabel, plural, timeAgo } from './format.js';
|
|
11
13
|
/** How a client's status is drawn. */
|
|
12
14
|
const STATUS_LOOK = {
|
|
@@ -14,6 +16,29 @@ const STATUS_LOOK = {
|
|
|
14
16
|
idle: { label: 'Idle', variant: 'attention' },
|
|
15
17
|
disconnected: { label: 'Disconnected', variant: 'secondary' },
|
|
16
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* How a task's status is drawn.
|
|
21
|
+
*
|
|
22
|
+
* `input_required` is `attention` rather than `secondary` because it is the
|
|
23
|
+
* one status that is waiting on a person: a run stopped for an approval is
|
|
24
|
+
* not idling, it is asking, and it stays asking until somebody answers.
|
|
25
|
+
*/
|
|
26
|
+
const TASK_LOOK = {
|
|
27
|
+
working: { label: 'Working', variant: 'success' },
|
|
28
|
+
input_required: { label: 'Waiting on you', variant: 'attention' },
|
|
29
|
+
completed: { label: 'Completed', variant: 'secondary' },
|
|
30
|
+
failed: { label: 'Failed', variant: 'danger' },
|
|
31
|
+
cancelled: { label: 'Cancelled', variant: 'secondary' },
|
|
32
|
+
};
|
|
33
|
+
/** What a task is running, in one phrase. */
|
|
34
|
+
const taskContext = (task) => {
|
|
35
|
+
if (task.notebookUid) {
|
|
36
|
+
return task.cellId
|
|
37
|
+
? `Notebook ${task.notebookUid} · cell ${task.cellId}`
|
|
38
|
+
: `Notebook ${task.notebookUid}`;
|
|
39
|
+
}
|
|
40
|
+
return task.sandboxUid ? `Sandbox ${task.sandboxUid}` : '';
|
|
41
|
+
};
|
|
17
42
|
/** What a client is working on, in one phrase. */
|
|
18
43
|
const boundTo = (client) => {
|
|
19
44
|
const call = client.lastCall;
|
|
@@ -54,6 +79,10 @@ export const McpDashboard = ({ errorState, routes, org, showTitle = true, sectio
|
|
|
54
79
|
const terminate = useTerminateBinding();
|
|
55
80
|
const [disconnecting, setDisconnecting] = useState(null);
|
|
56
81
|
const [terminating, setTerminating] = useState(null);
|
|
82
|
+
const [sharing, setSharing] = useState(null);
|
|
83
|
+
// Runtimes owns a sandbox's sharing, and addresses it by the *runtime's*
|
|
84
|
+
// name rather than by the handle the session holds.
|
|
85
|
+
const runtimesUrl = useCoreStore(state => state.configuration.runtimesUrl || state.configuration.managerUrl || '');
|
|
57
86
|
// Primer 37 types Dialog's focus refs with React 18's non-nullable
|
|
58
87
|
// `RefObject<HTMLElement>`. React 19's `useRef(null)` is nullable, and this
|
|
59
88
|
// ref is only handed to Dialog, so narrow it once here.
|
|
@@ -71,6 +100,27 @@ export const McpDashboard = ({ errorState, routes, org, showTitle = true, sectio
|
|
|
71
100
|
status: clientStatusOf(client.lastCall?.at),
|
|
72
101
|
})), [data?.clients]);
|
|
73
102
|
const sandboxes = useMemo(() => (data?.sandboxes ?? []).map(binding => ({ ...binding, id: binding.uid })), [data?.sandboxes]);
|
|
103
|
+
/**
|
|
104
|
+
* The runs, the ones asking for a person first.
|
|
105
|
+
*
|
|
106
|
+
* `input_required` is a run that has stopped and will not move until
|
|
107
|
+
* somebody answers it, so it goes to the top however long it has been
|
|
108
|
+
* there; `working` next; then whatever has just finished, newest first. A
|
|
109
|
+
* list ordered only by time buries the one row that needs a hand.
|
|
110
|
+
*/
|
|
111
|
+
const tasks = useMemo(() => {
|
|
112
|
+
const rank = {
|
|
113
|
+
input_required: 0,
|
|
114
|
+
working: 1,
|
|
115
|
+
failed: 2,
|
|
116
|
+
completed: 3,
|
|
117
|
+
cancelled: 3,
|
|
118
|
+
};
|
|
119
|
+
return [...(data?.tasks ?? [])]
|
|
120
|
+
.map(task => ({ ...task, id: task.uid }))
|
|
121
|
+
.sort((left, right) => rank[left.status] - rank[right.status] ||
|
|
122
|
+
right.lastUpdatedAt.localeCompare(left.lastUpdatedAt));
|
|
123
|
+
}, [data?.tasks]);
|
|
74
124
|
const calls = useMemo(() => (data?.calls ?? []).map(call => ({ ...call, id: call.uid })), [data?.calls]);
|
|
75
125
|
const filteredCalls = useMemo(() => calls.filter(call => (!callClient || call.clientId === callClient) &&
|
|
76
126
|
(!callTool || call.tool === callTool) &&
|
|
@@ -189,6 +239,41 @@ export const McpDashboard = ({ errorState, routes, org, showTitle = true, sectio
|
|
|
189
239
|
renderCell: row => (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Button, { variant: "invisible", size: "small", "aria-label": `Actions for ${row.clientName || row.clientId}`, icon: KebabHorizontalIcon }) }), _jsx(ActionMenu.Overlay, { align: "end", children: _jsxs(ActionList, { children: [_jsx(ActionList.Item, { onSelect: () => navigate(`${routes.audit}?agent=${encodeURIComponent(row.clientId)}`), children: "Audit" }), _jsx(ActionList.Divider, {}), _jsx(ActionList.Item, { variant: "danger", disabled: !row.grantUid, onSelect: () => setDisconnecting(row), children: "Disconnect" })] }) })] })),
|
|
190
240
|
},
|
|
191
241
|
];
|
|
242
|
+
const taskColumns = [
|
|
243
|
+
{
|
|
244
|
+
header: 'Run',
|
|
245
|
+
field: 'uid',
|
|
246
|
+
rowHeader: true,
|
|
247
|
+
renderCell: row => (_jsx(Link, { href: `${routes.runs}/${encodeURIComponent(row.uid)}`, onClick: event => {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
navigate(`${routes.runs}/${encodeURIComponent(row.uid)}`);
|
|
250
|
+
}, sx: { fontFamily: 'mono', fontSize: 0 }, children: row.uid })),
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
header: 'Status',
|
|
254
|
+
id: 'status',
|
|
255
|
+
width: '150px',
|
|
256
|
+
renderCell: row => (_jsx(Label, { size: "small", variant: TASK_LOOK[row.status].variant, children: TASK_LOOK[row.status].label })),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
header: 'Tool',
|
|
260
|
+
id: 'tool',
|
|
261
|
+
width: '160px',
|
|
262
|
+
renderCell: row => _jsx(Text, { sx: { fontSize: 0 }, children: row.tool || '—' }),
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
header: 'Running',
|
|
266
|
+
id: 'context',
|
|
267
|
+
width: 'growCollapse',
|
|
268
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: taskContext(row) || '—' })),
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
header: 'Updated',
|
|
272
|
+
id: 'updated',
|
|
273
|
+
width: '140px',
|
|
274
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: _jsx(RelativeTime, { datetime: row.lastUpdatedAt }) })),
|
|
275
|
+
},
|
|
276
|
+
];
|
|
192
277
|
const sandboxColumns = [
|
|
193
278
|
{
|
|
194
279
|
header: 'Handle',
|
|
@@ -226,9 +311,9 @@ export const McpDashboard = ({ errorState, routes, org, showTitle = true, sectio
|
|
|
226
311
|
{
|
|
227
312
|
header: '',
|
|
228
313
|
id: 'actions',
|
|
229
|
-
width: '
|
|
314
|
+
width: '190px',
|
|
230
315
|
align: 'end',
|
|
231
|
-
renderCell: row => (_jsx(Button, { size: "small", variant: "danger", disabled: row.state === 'closed', onClick: () => setTerminating(row), children: "Terminate" })),
|
|
316
|
+
renderCell: row => (_jsxs(Box, { sx: { display: 'flex', gap: 2, justifyContent: 'flex-end' }, children: [sandboxSharingUrl(runtimesUrl, row.runtimeName) && (_jsx(Button, { size: "small", disabled: row.state === 'closed', onClick: () => setSharing(row), children: "Share" })), _jsx(Button, { size: "small", variant: "danger", disabled: row.state === 'closed', onClick: () => setTerminating(row), children: "Terminate" })] })),
|
|
232
317
|
},
|
|
233
318
|
];
|
|
234
319
|
const callColumns = [
|
|
@@ -299,9 +384,7 @@ export const McpDashboard = ({ errorState, routes, org, showTitle = true, sectio
|
|
|
299
384
|
(clients.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-clients", children: "Connected clients" }), _jsxs(Table.Subtitle, { as: "p", id: "mcp-clients-subtitle", children: [plural(clients.length, 'client'), " with a live grant on your workspace."] }), _jsx(DataTable, { "aria-labelledby": "mcp-clients", "aria-describedby": "mcp-clients-subtitle", data: clients, columns: clientColumns })] })) : (_jsx(PanelEmpty, { icon: PlugIcon, heading: "No client connected", description: "Connect Claude Code, Codex, Cursor, VS Code or any MCP client to your workspace, and it appears here on its first call.", action: {
|
|
300
385
|
label: 'Set up a client',
|
|
301
386
|
onClick: () => navigate(routes.access),
|
|
302
|
-
} }))), draws('runs') && !loading && (
|
|
303
|
-
? 'Tasks are running, and the live pane that shows their progress, their cell and their approvals arrives with durable execution in milestone 2.'
|
|
304
|
-
: 'A tool call that outlives its request becomes a task you can watch, cancel and answer. Durable execution arrives in milestone 2; until then every call finishes inside its own request and is listed under Recent calls.' })] })), draws('sandboxes') &&
|
|
387
|
+
} }))), draws('runs') && !loading && (_jsx(Box, { children: tasks.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-runs", children: "Running now" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-runs-subtitle", children: "A tool call that outlives its request. Open one to watch its output, cancel it, or answer it when it is waiting on you." }), _jsx(DataTable, { "aria-labelledby": "mcp-runs", "aria-describedby": "mcp-runs-subtitle", data: tasks, columns: taskColumns })] })) : (_jsx(PanelEmpty, { icon: PlayIcon, heading: "Nothing is running", description: "A tool call that outlives its request becomes a task you can watch, cancel and answer. Ask a client for one \u2014 an agent that runs a long cell \u2014 and it appears here while it runs." })) })), draws('sandboxes') &&
|
|
305
388
|
!loading &&
|
|
306
389
|
(sandboxes.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-sandboxes", children: "Sandboxes bound" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-sandboxes-subtitle", children: "The sessions your agents execute in. Terminating one ends its open work." }), _jsx(DataTable, { "aria-labelledby": "mcp-sandboxes", "aria-describedby": "mcp-sandboxes-subtitle", data: sandboxes, columns: sandboxColumns })] })) : (_jsx(PanelEmpty, { icon: StackIcon, heading: "No sandbox bound", description: "An agent that launches a sandbox keeps it for the session; it appears here with its provider, its capabilities and what it is doing." }))), draws('calls') &&
|
|
307
390
|
!loading &&
|
|
@@ -342,6 +425,6 @@ export const McpDashboard = ({ errorState, routes, org, showTitle = true, sectio
|
|
|
342
425
|
onClick: confirmTerminate,
|
|
343
426
|
disabled: terminate.isPending,
|
|
344
427
|
},
|
|
345
|
-
], children: _jsxs(Text, { sx: { fontSize: 1 }, children: ["The runtime behind ", terminating.uid, " is stopped and its session closed. Work the agent has open there ends as", ' ', _jsx("code", { children: "SANDBOX_LOST" }), "; anything already written to a notebook stays."] }) }))] }));
|
|
428
|
+
], children: _jsxs(Text, { sx: { fontSize: 1 }, children: ["The runtime behind ", terminating.uid, " is stopped and its session closed. Work the agent has open there ends as", ' ', _jsx("code", { children: "SANDBOX_LOST" }), "; anything already written to a notebook stays."] }) })), sandboxSharingUrl(runtimesUrl, sharing?.runtimeName) && (_jsx(ShareAccessDialog, { isOpen: true, requestUrl: sandboxSharingUrl(runtimesUrl, sharing?.runtimeName), resourceLabel: "Sandbox", resourceName: sharing?.alias || sharing?.uid, resourceDescription: "Who else may look at this sandbox, change it, or run code on it. Sharing ends when the sandbox does.", onClose: () => setSharing(null) }))] }));
|
|
346
429
|
};
|
|
347
430
|
export default McpDashboard;
|
package/lib/views/mcp/McpHome.js
CHANGED
|
@@ -75,7 +75,7 @@ const Summary = ({ icon: Icon, title, children, action, }) => (_jsxs(Box, { sx:
|
|
|
75
75
|
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Icon, { size: 16 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: title })] }), _jsx(Box, { sx: { fontSize: 0, color: 'fg.muted', display: 'grid', gap: 1 }, children: children }), action && (_jsx(Box, { children: _jsx(Link, { as: "button", sx: { fontSize: 0, cursor: 'pointer' }, onClick: action.onClick, children: action.label }) }))] }));
|
|
76
76
|
export const McpHome = ({ routes, clientDocsBase = '/docs/mcp-clients', showTitle = true, showSummaries = true, showSetup = true, onOpenDocs, }) => {
|
|
77
77
|
const navigate = useNavigate();
|
|
78
|
-
const endpoint = useCoreStore(state => state.configuration.
|
|
78
|
+
const endpoint = useCoreStore(state => state.configuration.datalayerMcpServerUrl);
|
|
79
79
|
// Whether there is a token, never what it is: this page shows an address
|
|
80
80
|
// and a status, and a credential belongs on neither.
|
|
81
81
|
const signedIn = Boolean(useIAMStore(state => state.token));
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
* @module views/mcp/McpObservability
|
|
22
22
|
*/
|
|
23
23
|
import type { JSX } from 'react';
|
|
24
|
+
import type { McpWorkflowsHealth } from '../../api/mcp/operations';
|
|
24
25
|
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
25
|
-
export type McpObservabilityPane = 'runs' | 'metrics';
|
|
26
|
+
export type McpObservabilityPane = 'runs' | 'live' | 'metrics';
|
|
26
27
|
export interface McpObservabilityProps {
|
|
27
28
|
/** The application's words for a failed request. */
|
|
28
29
|
errorState: McpErrorStateFn;
|
|
@@ -46,5 +47,33 @@ export interface McpObservabilityProps {
|
|
|
46
47
|
org?: string;
|
|
47
48
|
showTitle?: boolean;
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* What part of the platform a span belongs to, from its name.
|
|
52
|
+
*
|
|
53
|
+
* By prefix rather than by service, because one service emits spans for more
|
|
54
|
+
* than one stage — the gateway does its own policy check — and because a
|
|
55
|
+
* durable run's spans arrive from a process the gateway never talks to. A
|
|
56
|
+
* name nothing recognises gets no label rather than a wrong one: the span is
|
|
57
|
+
* still drawn, still timed, and still named.
|
|
58
|
+
*/
|
|
59
|
+
export declare const stageOf: (name: string) => string;
|
|
60
|
+
/**
|
|
61
|
+
* What the durable engine's own report amounts to, in one line.
|
|
62
|
+
*
|
|
63
|
+
* Three states, not two. "Unreachable" is deliberately not "unhealthy": the
|
|
64
|
+
* gateway answers `/operations/workflows` even when it cannot reach the
|
|
65
|
+
* engine, precisely so an operator can tell "the engine says it is unwell"
|
|
66
|
+
* from "nobody could ask it" — the first is a problem with the engine, the
|
|
67
|
+
* second is usually a problem with the network between them, and sending
|
|
68
|
+
* somebody to the wrong one costs an hour.
|
|
69
|
+
*
|
|
70
|
+
* A healthy engine with a backlog is still healthy, and says so with the
|
|
71
|
+
* backlog beside it: a queue is a queue, not a fault.
|
|
72
|
+
*/
|
|
73
|
+
export declare const engineLook: (health: McpWorkflowsHealth | undefined, failed: boolean) => {
|
|
74
|
+
label: string;
|
|
75
|
+
variant: "success" | "attention" | "danger" | "secondary";
|
|
76
|
+
note: string;
|
|
77
|
+
};
|
|
49
78
|
export declare const McpObservability: ({ errorState, routes, pane, onPaneChange, taskUid, traceId, onSelect, agent, org, showTitle, }: McpObservabilityProps) => JSX.Element;
|
|
50
79
|
export default McpObservability;
|