@datalayer/core 1.1.66 → 1.2.10
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 +41 -18
- 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/connectedAgents.d.ts +29 -0
- package/lib/api/iam/connectedAgents.js +35 -0
- 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/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 +9 -21
- 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 +25 -9
- package/lib/api/mcp/observability.js +63 -50
- 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/constants.d.ts +1 -0
- package/lib/client/constants.js +1 -0
- 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/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/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 +41 -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/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/useBillingEntityStore.js +3 -0
- package/lib/hooks/useCache.d.ts +168 -25
- package/lib/hooks/useCache.js +1424 -307
- 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 +247 -12
- package/lib/hooks/useNavigate.d.ts +41 -3
- package/lib/hooks/useNavigate.js +106 -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/models/CreditsDTO.d.ts +6 -5
- package/lib/models/CreditsDTO.js +7 -5
- 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/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 -3
- package/lib/state/substates/IAMState.js +7 -0
- package/lib/state/substates/LayoutState.js +12 -0
- package/lib/state/substates/ProfileState.d.ts +1 -0
- package/lib/state/substates/ProfileState.js +1 -0
- package/lib/utils/Lazy.d.ts +20 -0
- package/lib/utils/Lazy.js +32 -6
- package/lib/utils/Screencapture.js +3 -0
- 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/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 +269 -264
- package/scripts/generate-mcp-types.py +1 -1
- package/scripts/generate-orchestration-types.py +920 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Button, Heading, Label, Spinner, Text, Truncate, } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
6
|
+
import { useContentSources, useMcpSessions, useMcpTools, useRevokeMcpSession, } from '../../hooks/useContents.js';
|
|
7
|
+
/**
|
|
8
|
+
* The tools a session can really call: what it was granted, still allowed by
|
|
9
|
+
* the source.
|
|
10
|
+
*
|
|
11
|
+
* The same intersection Contents applies, computed here so the page shows
|
|
12
|
+
* what will happen rather than what was asked for. Sorted, so two sessions
|
|
13
|
+
* of the same source read the same way.
|
|
14
|
+
*/
|
|
15
|
+
export const effectiveTools = (sessionAllowed, sourceAllowed) => {
|
|
16
|
+
const source = new Set(sourceAllowed ?? []);
|
|
17
|
+
return [...new Set(sessionAllowed ?? [])].filter(tool => source.has(tool)).sort();
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The tools a session still lists that its source has stopped allowing.
|
|
21
|
+
*
|
|
22
|
+
* Worth its own answer rather than a subtraction at the call site: this is
|
|
23
|
+
* the set that makes an agent fail in a way nobody can explain from either
|
|
24
|
+
* record alone, and naming it is the whole reason to read the two together.
|
|
25
|
+
*/
|
|
26
|
+
export const withdrawnTools = (sessionAllowed, sourceAllowed) => {
|
|
27
|
+
const source = new Set(sourceAllowed ?? []);
|
|
28
|
+
return [...new Set(sessionAllowed ?? [])].filter(tool => !source.has(tool)).sort();
|
|
29
|
+
};
|
|
30
|
+
/** How a source's approval policy reads to somebody deciding whether to trust it. */
|
|
31
|
+
export const APPROVAL_POLICY_LOOK = {
|
|
32
|
+
explicit: {
|
|
33
|
+
label: 'Approval required',
|
|
34
|
+
variant: 'attention',
|
|
35
|
+
note: 'Every call waits for somebody to allow it.',
|
|
36
|
+
},
|
|
37
|
+
'auto-allowlisted': {
|
|
38
|
+
label: 'Allowlisted',
|
|
39
|
+
variant: 'success',
|
|
40
|
+
note: 'Calls to allowed tools run; anything else is refused.',
|
|
41
|
+
},
|
|
42
|
+
never: {
|
|
43
|
+
label: 'No approval',
|
|
44
|
+
variant: 'secondary',
|
|
45
|
+
note: 'Calls to allowed tools run without asking.',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
/** The MCP configuration of a source, where it has one. */
|
|
49
|
+
export const mcpConfigOf = (source) => source.kind === 'mcp'
|
|
50
|
+
? source.configuration
|
|
51
|
+
: undefined;
|
|
52
|
+
const SessionRow = ({ session, sourceTools, mayRevoke, onRevoke, revoking, }) => {
|
|
53
|
+
const effective = effectiveTools(session.allowedTools, sourceTools);
|
|
54
|
+
const withdrawn = withdrawnTools(session.allowedTools, sourceTools);
|
|
55
|
+
return (_jsxs(Box, { sx: {
|
|
56
|
+
display: 'grid',
|
|
57
|
+
gap: 1,
|
|
58
|
+
py: 2,
|
|
59
|
+
borderTop: '1px solid',
|
|
60
|
+
borderColor: 'border.muted',
|
|
61
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx(Label, { size: "small", variant: session.status === 'active' ? 'success' : 'secondary', children: session.status }), _jsx(Truncate, { title: session.actorUid, sx: { fontSize: 0, fontFamily: 'mono', maxWidth: '16rem' }, children: session.actorUid }), session.status === 'active' && mayRevoke && (_jsx(Button, { size: "small", variant: "danger", disabled: revoking, onClick: onRevoke, children: revoking ? 'Revoking…' : 'Revoke' }))] }), effective.length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'attention.fg' }, children: "This session can call nothing. An empty allowlist allows no tool \u2014 it does not mean unrestricted." })) : (_jsx(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: effective.map(tool => (_jsx(Label, { size: "small", variant: "secondary", children: tool }, tool))) })), withdrawn.length > 0 && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Refused at every call, because the source no longer allows", ' ', withdrawn.join(', '), " \u2014 the session still lists it."] }))] }));
|
|
62
|
+
};
|
|
63
|
+
const SourceCard = ({ source, permissions, errorState, }) => {
|
|
64
|
+
const config = mcpConfigOf(source);
|
|
65
|
+
const sourceTools = config?.allowedTools ?? [];
|
|
66
|
+
const sessions = useMcpSessions({ sourceUid: source.uid });
|
|
67
|
+
const revoke = useRevokeMcpSession();
|
|
68
|
+
const manifest = useMcpTools(source.uid);
|
|
69
|
+
const [revoking, setRevoking] = useState('');
|
|
70
|
+
const look = config ? APPROVAL_POLICY_LOOK[config.approvalPolicy] : undefined;
|
|
71
|
+
const offered = manifest.data?.tools?.length ?? 0;
|
|
72
|
+
return (_jsxs(Box, { sx: {
|
|
73
|
+
border: '1px solid',
|
|
74
|
+
borderColor: 'border.default',
|
|
75
|
+
borderRadius: 2,
|
|
76
|
+
p: 3,
|
|
77
|
+
display: 'grid',
|
|
78
|
+
gap: 2,
|
|
79
|
+
minWidth: 0,
|
|
80
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: source.name }), look && (_jsx(Label, { size: "small", variant: look.variant, children: look.label })), config?.transport && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: config.transport }))] }), look && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: look.note })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: sourceTools.length === 0
|
|
81
|
+
? 'No tool is allowed on this source, so an agent can call none of it.'
|
|
82
|
+
: `${sourceTools.length} of ${offered || sourceTools.length} tools allowed to agents.` }), sourceTools.length > 0 && (_jsx(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [...sourceTools].sort().map(tool => (_jsx(Label, { size: "small", variant: "accent", children: tool }, tool))) })), _jsxs(Box, { children: [_jsx(Heading, { as: "h4", sx: { fontSize: 0, color: 'fg.muted', mb: 1 }, children: "Open sessions" }), sessions.isError ? (_jsx(McpErrorBlankslate, { state: errorState(sessions.error, 'The sessions'), onRetry: () => sessions.refetch() })) : sessions.isPending ? (_jsx(Spinner, { size: "small" })) : (sessions.data?.items.length ?? 0) === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "No agent has opened this source." })) : ((sessions.data?.items ?? []).map(session => (_jsx(SessionRow, { session: session, sourceTools: sourceTools,
|
|
83
|
+
// Revoking a session withdraws an agent's access to this
|
|
84
|
+
// source, so it follows the source's own write permission
|
|
85
|
+
// rather than being offered to everyone who can read the page.
|
|
86
|
+
mayRevoke: permissions.update || permissions.isOwner, revoking: revoking === session.uid, onRevoke: () => {
|
|
87
|
+
setRevoking(session.uid);
|
|
88
|
+
revoke.mutate(session.uid, {
|
|
89
|
+
onSettled: () => setRevoking(''),
|
|
90
|
+
});
|
|
91
|
+
} }, session.uid))))] })] }));
|
|
92
|
+
};
|
|
93
|
+
export const ToolAccess = ({ errorState, spaceUid, }) => {
|
|
94
|
+
const sources = useContentSources({ kind: 'mcp', ...(spaceUid ? { spaceUid } : {}) });
|
|
95
|
+
// The listing wraps each source with what this caller may do with it, and
|
|
96
|
+
// both halves are used: the source to read the allowlist, the permissions
|
|
97
|
+
// to decide whether revoking is offered at all.
|
|
98
|
+
const items = (sources.data?.items ?? []);
|
|
99
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Tools" }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: "The MCP sources your agents may reach, the tools allowed on each, and the sessions open against them. A session can only narrow what the source allows, never widen it." })] }), sources.isError ? (_jsx(McpErrorBlankslate, { state: errorState(sources.error, 'The sources'), onRetry: () => sources.refetch() })) : sources.isPending ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : items.length === 0 ? (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "No MCP source is connected here. A source has to be created before its tools can be offered to an agent." })) : (items.map(entry => (_jsx(SourceCard, { source: entry.source, permissions: entry.permissions, errorState: errorState }, entry.source.uid))))] }));
|
|
100
|
+
};
|
|
101
|
+
export default ToolAccess;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a run's time went.
|
|
3
|
+
*
|
|
4
|
+
* A run crosses four processes — the gateway that accepted the call, the
|
|
5
|
+
* durable engine that recorded it, the worker that executed it, and the
|
|
6
|
+
* sandbox that ran the code — and the question a person actually has when a
|
|
7
|
+
* run took eleven minutes is *which of them* took the eleven minutes. A list
|
|
8
|
+
* of spans does not answer that; the same spans laid against one clock do.
|
|
9
|
+
*
|
|
10
|
+
* **The bars are proportional, the rows are the tree.** Nesting comes from
|
|
11
|
+
* `parent_span_id`, so a step that waited on a sandbox sits under the step
|
|
12
|
+
* that waited, and its bar sits inside its parent's — a gap between a
|
|
13
|
+
* parent's bar and its children's is queue time, which is the thing worth
|
|
14
|
+
* seeing and the thing a table of durations hides.
|
|
15
|
+
*
|
|
16
|
+
* **It draws spans from every service, not the gateway's alone.** The
|
|
17
|
+
* `durable.*` and `runtimes.*` spans are the ones that explain a slow run;
|
|
18
|
+
* showing only what the gateway emitted would show the eleven minutes as one
|
|
19
|
+
* unexplained bar. Services are told apart by colour and named on the row.
|
|
20
|
+
*
|
|
21
|
+
* @module views/mcp/TraceTimeline
|
|
22
|
+
*/
|
|
23
|
+
import type { JSX } from 'react';
|
|
24
|
+
import type { OtelSpan } from '../../api/otel/types';
|
|
25
|
+
import type { McpSpanNode } from '../../api/mcp/observability';
|
|
26
|
+
/**
|
|
27
|
+
* The narrowest a bar is drawn, as a percentage of the run.
|
|
28
|
+
*
|
|
29
|
+
* A 2ms authorization check inside a ten-minute run is 0.003% wide, which is
|
|
30
|
+
* nothing at all — and the check happening is exactly what somebody reading
|
|
31
|
+
* a trace is looking for. It is drawn as a mark rather than dropped; the
|
|
32
|
+
* label carries the real duration.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MIN_BAR_PERCENT = 0.75;
|
|
35
|
+
/** One span, placed against the run's clock. */
|
|
36
|
+
export interface TimelineRow {
|
|
37
|
+
span: OtelSpan;
|
|
38
|
+
/** How deep in the span tree, for the indent. */
|
|
39
|
+
depth: number;
|
|
40
|
+
/** Where the bar starts, as a percentage of the whole run. */
|
|
41
|
+
left: number;
|
|
42
|
+
/** How wide it is, as a percentage. Never zero — an instant is still a mark. */
|
|
43
|
+
width: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The spans of a trace, flattened depth-first and placed against one clock.
|
|
47
|
+
*
|
|
48
|
+
* Three cases are not the ordinary one and each is drawn rather than
|
|
49
|
+
* dropped, because a trace a person opens is a trace they need to read:
|
|
50
|
+
*
|
|
51
|
+
* - a span whose timestamps do not parse is placed at the left as a mark —
|
|
52
|
+
* it happened, we cannot say when;
|
|
53
|
+
* - a trace with no duration at all — one instant, or a single unfinished
|
|
54
|
+
* span — gives every bar the full width, since there is nothing to divide;
|
|
55
|
+
* - a child that starts before its parent, which clock skew between two
|
|
56
|
+
* services makes real, is clamped into the run rather than drawn off the
|
|
57
|
+
* end of it.
|
|
58
|
+
*/
|
|
59
|
+
export declare const timelineRows: (tree: McpSpanNode[]) => TimelineRow[];
|
|
60
|
+
/**
|
|
61
|
+
* A colour per service, so the four processes are told apart at a glance.
|
|
62
|
+
*
|
|
63
|
+
* Named by what the service *is* rather than by its span prefix: the same
|
|
64
|
+
* palette has to hold when a fifth service starts emitting, and the fallback
|
|
65
|
+
* is a neutral rather than a collision.
|
|
66
|
+
*/
|
|
67
|
+
export declare const SERVICE_TONE: Record<string, string>;
|
|
68
|
+
/** The bar colour of a span, by the service that emitted it and how it ended. */
|
|
69
|
+
export declare const toneOf: (span: OtelSpan) => string;
|
|
70
|
+
export interface TraceTimelineProps {
|
|
71
|
+
/** The spans, already parented — `McpRunTrace.tree`. */
|
|
72
|
+
tree: McpSpanNode[];
|
|
73
|
+
/** The trace these are of, shown so it can be copied into a log query. */
|
|
74
|
+
traceId?: string;
|
|
75
|
+
/** Whether the run is still going, which is why the trace may be short. */
|
|
76
|
+
live?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export declare const TraceTimeline: ({ tree, traceId, live, }: TraceTimelineProps) => JSX.Element;
|
|
79
|
+
/** The timeline under its own heading, as the Runs detail draws it. */
|
|
80
|
+
export declare const TraceTimelineSection: (props: TraceTimelineProps) => JSX.Element;
|
|
81
|
+
export default TraceTimeline;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Heading, Label, Text, Truncate } from '@primer/react';
|
|
3
|
+
import { Box } from '@datalayer/primer-addons';
|
|
4
|
+
import { durationLabel } from './format.js';
|
|
5
|
+
/**
|
|
6
|
+
* The narrowest a bar is drawn, as a percentage of the run.
|
|
7
|
+
*
|
|
8
|
+
* A 2ms authorization check inside a ten-minute run is 0.003% wide, which is
|
|
9
|
+
* nothing at all — and the check happening is exactly what somebody reading
|
|
10
|
+
* a trace is looking for. It is drawn as a mark rather than dropped; the
|
|
11
|
+
* label carries the real duration.
|
|
12
|
+
*/
|
|
13
|
+
export const MIN_BAR_PERCENT = 0.75;
|
|
14
|
+
/**
|
|
15
|
+
* The instants a span covers, as epoch milliseconds.
|
|
16
|
+
*
|
|
17
|
+
* `end` is never before `start`, and one comparison is enough for both ways
|
|
18
|
+
* that happens: an end that will not parse is `NaN`, and every comparison
|
|
19
|
+
* with `NaN` is false, so it falls to `start` along with an end that is
|
|
20
|
+
* merely backwards. A `Number.isFinite` check beside it reads as belt and
|
|
21
|
+
* braces but is dead code — which is what the mutation run showed.
|
|
22
|
+
*
|
|
23
|
+
* The `NaN` case is the one that matters. A single unparseable timestamp
|
|
24
|
+
* left in would poison the window every other bar is measured against, and
|
|
25
|
+
* the timeline would go on drawing — as a column of identical marks.
|
|
26
|
+
*/
|
|
27
|
+
const extentOf = (span) => {
|
|
28
|
+
const start = Date.parse(span.start_time);
|
|
29
|
+
const end = Date.parse(span.end_time);
|
|
30
|
+
return { start, end: end > start ? end : start };
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The spans of a trace, flattened depth-first and placed against one clock.
|
|
34
|
+
*
|
|
35
|
+
* Three cases are not the ordinary one and each is drawn rather than
|
|
36
|
+
* dropped, because a trace a person opens is a trace they need to read:
|
|
37
|
+
*
|
|
38
|
+
* - a span whose timestamps do not parse is placed at the left as a mark —
|
|
39
|
+
* it happened, we cannot say when;
|
|
40
|
+
* - a trace with no duration at all — one instant, or a single unfinished
|
|
41
|
+
* span — gives every bar the full width, since there is nothing to divide;
|
|
42
|
+
* - a child that starts before its parent, which clock skew between two
|
|
43
|
+
* services makes real, is clamped into the run rather than drawn off the
|
|
44
|
+
* end of it.
|
|
45
|
+
*/
|
|
46
|
+
export const timelineRows = (tree) => {
|
|
47
|
+
const flat = [];
|
|
48
|
+
const walk = (nodes, depth) => {
|
|
49
|
+
for (const node of nodes) {
|
|
50
|
+
flat.push({ span: node.span, depth });
|
|
51
|
+
walk(node.children, depth + 1);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
walk(tree, 0);
|
|
55
|
+
let first = Number.POSITIVE_INFINITY;
|
|
56
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
57
|
+
for (const { span } of flat) {
|
|
58
|
+
const { start, end } = extentOf(span);
|
|
59
|
+
if (Number.isFinite(start)) {
|
|
60
|
+
first = Math.min(first, start);
|
|
61
|
+
last = Math.max(last, end);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const total = last - first;
|
|
65
|
+
return flat.map(({ span, depth }) => {
|
|
66
|
+
const { start, end } = extentOf(span);
|
|
67
|
+
if (!Number.isFinite(start) || !Number.isFinite(total) || total <= 0) {
|
|
68
|
+
// No clock to divide: an unplaceable span is a mark, and a run with no
|
|
69
|
+
// duration is one bar across.
|
|
70
|
+
const placeable = Number.isFinite(start) && Number.isFinite(total);
|
|
71
|
+
return { span, depth, left: 0, width: placeable ? 100 : MIN_BAR_PERCENT };
|
|
72
|
+
}
|
|
73
|
+
// The width is settled first, then the bar is slid left far enough to
|
|
74
|
+
// hold it: a zero-length span at the very end of a run is a mark that
|
|
75
|
+
// has to fit inside the track, not one hanging off its right edge.
|
|
76
|
+
const width = Math.max(MIN_BAR_PERCENT, ((end - start) / total) * 100);
|
|
77
|
+
const left = Math.min(100 - width, Math.max(0, ((start - first) / total) * 100));
|
|
78
|
+
return { span, depth, left, width };
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A colour per service, so the four processes are told apart at a glance.
|
|
83
|
+
*
|
|
84
|
+
* Named by what the service *is* rather than by its span prefix: the same
|
|
85
|
+
* palette has to hold when a fifth service starts emitting, and the fallback
|
|
86
|
+
* is a neutral rather than a collision.
|
|
87
|
+
*/
|
|
88
|
+
export const SERVICE_TONE = {
|
|
89
|
+
'jupyter-mcp-server': 'accent.emphasis',
|
|
90
|
+
'datalayer-durable': 'done.emphasis',
|
|
91
|
+
'datalayer-runtimes': 'success.emphasis',
|
|
92
|
+
'datalayer-iam': 'severe.emphasis',
|
|
93
|
+
};
|
|
94
|
+
/** The bar colour of a span, by the service that emitted it and how it ended. */
|
|
95
|
+
export const toneOf = (span) => {
|
|
96
|
+
if (span.status_code === 'STATUS_CODE_ERROR' || span.status_code === 'ERROR') {
|
|
97
|
+
return 'danger.emphasis';
|
|
98
|
+
}
|
|
99
|
+
return SERVICE_TONE[span.service_name] ?? 'neutral.emphasis';
|
|
100
|
+
};
|
|
101
|
+
export const TraceTimeline = ({ tree, traceId, live = false, }) => {
|
|
102
|
+
const rows = timelineRows(tree);
|
|
103
|
+
if (rows.length === 0) {
|
|
104
|
+
return (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: live
|
|
105
|
+
? 'No spans yet. They arrive as the services report them.'
|
|
106
|
+
: 'This run reported no spans. A run started before tracing reached the service that ran it has none.' }));
|
|
107
|
+
}
|
|
108
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 1, minWidth: 0 }, children: [traceId && (_jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'fg.muted' }, children: traceId })), rows.map(row => (_jsxs(Box, { sx: {
|
|
109
|
+
display: 'grid',
|
|
110
|
+
gridTemplateColumns: 'minmax(0, 14rem) 1fr auto',
|
|
111
|
+
gap: 2,
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
minWidth: 0,
|
|
114
|
+
}, children: [_jsx(Box, { sx: { pl: row.depth, minWidth: 0 }, children: _jsx(Truncate, { title: `${row.span.span_name} · ${row.span.service_name}`, sx: { fontSize: 0, maxWidth: '100%' }, children: row.span.span_name }) }), _jsx(Box, { sx: {
|
|
115
|
+
position: 'relative',
|
|
116
|
+
height: '0.75rem',
|
|
117
|
+
bg: 'canvas.subtle',
|
|
118
|
+
borderRadius: 1,
|
|
119
|
+
minWidth: 0,
|
|
120
|
+
}, children: _jsx(Box, { sx: {
|
|
121
|
+
position: 'absolute',
|
|
122
|
+
top: 0,
|
|
123
|
+
bottom: 0,
|
|
124
|
+
left: `${row.left}%`,
|
|
125
|
+
width: `${row.width}%`,
|
|
126
|
+
bg: toneOf(row.span),
|
|
127
|
+
borderRadius: 1,
|
|
128
|
+
}, title: `${row.span.service_name} · ${durationLabel(row.span.duration_ms)}` }) }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontVariantNumeric: 'tabular-nums' }, children: durationLabel(row.span.duration_ms) })] }, row.span.span_id)))] }));
|
|
129
|
+
};
|
|
130
|
+
/** The timeline under its own heading, as the Runs detail draws it. */
|
|
131
|
+
export const TraceTimelineSection = (props) => (_jsxs(Box, { children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center', mb: 2 }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "Where the time went" }), props.live && (_jsx(Label, { size: "small", variant: "success", children: "Live" }))] }), _jsx(TraceTimeline, { ...props })] }));
|
|
132
|
+
export default TraceTimeline;
|
package/lib/views/mcp/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export * from './format';
|
|
|
14
14
|
export * from './McpDashboard';
|
|
15
15
|
export * from './McpHome';
|
|
16
16
|
export * from './ConnectedAgents';
|
|
17
|
+
export * from './RunDetail';
|
|
18
|
+
export * from './NotebookRuns';
|
|
17
19
|
export * from './AlertDestinations';
|
|
18
20
|
export * from './AlertRules';
|
|
19
21
|
export * from './OrganizationPolicy';
|
|
@@ -27,3 +29,6 @@ export * from './McpObservability';
|
|
|
27
29
|
export * from './Policies';
|
|
28
30
|
export * from './OrganizationUsage';
|
|
29
31
|
export * from './EnterpriseConsole';
|
|
32
|
+
export * from './TraceTimeline';
|
|
33
|
+
export * from './ApprovalQueue';
|
|
34
|
+
export * from './ToolAccess';
|
package/lib/views/mcp/index.js
CHANGED
|
@@ -22,6 +22,8 @@ export * from './format.js';
|
|
|
22
22
|
export * from './McpDashboard.js';
|
|
23
23
|
export * from './McpHome.js';
|
|
24
24
|
export * from './ConnectedAgents.js';
|
|
25
|
+
export * from './RunDetail.js';
|
|
26
|
+
export * from './NotebookRuns.js';
|
|
25
27
|
export * from './AlertDestinations.js';
|
|
26
28
|
export * from './AlertRules.js';
|
|
27
29
|
export * from './OrganizationPolicy.js';
|
|
@@ -35,3 +37,6 @@ export * from './McpObservability.js';
|
|
|
35
37
|
export * from './Policies.js';
|
|
36
38
|
export * from './OrganizationUsage.js';
|
|
37
39
|
export * from './EnterpriseConsole.js';
|
|
40
|
+
export * from './TraceTimeline.js';
|
|
41
|
+
export * from './ApprovalQueue.js';
|
|
42
|
+
export * from './ToolAccess.js';
|
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { create } from 'zustand';
|
|
16
16
|
import { persist } from 'zustand/middleware';
|
|
17
|
+
import { registerSessionState } from '../../state/sessionEnd.js';
|
|
17
18
|
export const useSimpleAuthStore = create()(persist(set => ({
|
|
18
19
|
token: null,
|
|
19
20
|
handle: null,
|
|
20
21
|
setAuth: (token, handle) => set({ token, handle }),
|
|
21
22
|
clearAuth: () => set({ token: null, handle: null }),
|
|
22
23
|
}), { name: 'datalayer-simple-auth' }));
|
|
24
|
+
// A token is the session's: forgotten when it ends.
|
|
25
|
+
registerSessionState({ forget: () => useSimpleAuthStore.getState().clearAuth() });
|
|
@@ -44,7 +44,8 @@ export const SecretEdit = ({ secretsListRoute = '/settings/secrets', } = {}) =>
|
|
|
44
44
|
name: secret.name || '',
|
|
45
45
|
nameConfirm: '',
|
|
46
46
|
description: secret.description || '',
|
|
47
|
-
|
|
47
|
+
// `secret.value` is already what its owner wrote (E3-05).
|
|
48
|
+
value: secret.value ?? '',
|
|
48
49
|
});
|
|
49
50
|
}
|
|
50
51
|
}, [secretQuery.data]);
|
|
@@ -97,7 +98,7 @@ export const SecretEdit = ({ secretsListRoute = '/settings/secrets', } = {}) =>
|
|
|
97
98
|
runStore.layout().showBackdrop('Updating the secret...');
|
|
98
99
|
secret.name = formValues.name;
|
|
99
100
|
secret.description = formValues.description;
|
|
100
|
-
secret.value =
|
|
101
|
+
secret.value = formValues.value;
|
|
101
102
|
updateSecretMutation.mutate(secret, {
|
|
102
103
|
onSuccess: (resp) => {
|
|
103
104
|
if (resp.success) {
|
|
@@ -76,7 +76,11 @@ export const SecretNew = ({ secretsListRoute = '/settings/secrets', } = {}) => {
|
|
|
76
76
|
variant: formValues.variant,
|
|
77
77
|
name: formValues.name,
|
|
78
78
|
description: formValues.description,
|
|
79
|
-
|
|
79
|
+
// As it is: a secret has one representation end to end
|
|
80
|
+
// (PLAN_ENVS.md E3-05). This used to `btoa` and nothing on the read
|
|
81
|
+
// side undid it, so a build secret arrived at its build encoded and
|
|
82
|
+
// opened nothing.
|
|
83
|
+
value: formValues.value,
|
|
80
84
|
}, {
|
|
81
85
|
onSuccess: (resp) => {
|
|
82
86
|
if (resp.success) {
|