@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.
Files changed (237) hide show
  1. package/README.md +43 -20
  2. package/lib/api/constants.d.ts +1 -1
  3. package/lib/api/constants.js +1 -1
  4. package/lib/api/contents/attachments.js +3 -7
  5. package/lib/api/contents/datasets.d.ts +48 -0
  6. package/lib/api/contents/datasets.js +44 -7
  7. package/lib/api/contents/runtimeMounts.js +1 -1
  8. package/lib/api/contents/sandboxUid.d.ts +13 -45
  9. package/lib/api/contents/sandboxUid.js +14 -65
  10. package/lib/api/evals/client.d.ts +402 -0
  11. package/lib/api/evals/client.js +359 -0
  12. package/lib/api/evals/index.d.ts +10 -0
  13. package/lib/api/evals/index.js +18 -0
  14. package/lib/api/evals/request.d.ts +43 -0
  15. package/lib/api/evals/request.js +67 -0
  16. package/lib/api/evals/status.d.ts +27 -0
  17. package/lib/api/evals/status.js +122 -0
  18. package/lib/api/evals/types.d.ts +1335 -0
  19. package/lib/api/evals/types.js +5 -0
  20. package/lib/api/iam/authentication.js +6 -4
  21. package/lib/api/iam/connectedAgents.d.ts +29 -0
  22. package/lib/api/iam/connectedAgents.js +35 -0
  23. package/lib/api/iam/healthz.js +2 -2
  24. package/lib/api/iam/identityProviders.d.ts +76 -0
  25. package/lib/api/iam/identityProviders.js +187 -0
  26. package/lib/api/iam/index.d.ts +1 -0
  27. package/lib/api/iam/index.js +1 -0
  28. package/lib/api/iam/mcpPolicy.d.ts +102 -1
  29. package/lib/api/iam/mcpPolicy.js +38 -2
  30. package/lib/api/iam/oauth2.js +18 -12
  31. package/lib/api/iam/profile.d.ts +17 -1
  32. package/lib/api/iam/profile.js +27 -0
  33. package/lib/api/iam/scimTokens.d.ts +79 -0
  34. package/lib/api/iam/scimTokens.js +141 -0
  35. package/lib/api/iam/secrets.js +10 -23
  36. package/lib/api/iam/trials.d.ts +38 -0
  37. package/lib/api/iam/trials.js +66 -0
  38. package/lib/api/index.d.ts +4 -0
  39. package/lib/api/index.js +4 -0
  40. package/lib/api/mcp/observability.d.ts +34 -11
  41. package/lib/api/mcp/observability.js +97 -59
  42. package/lib/api/orchestration/client.d.ts +53 -0
  43. package/lib/api/orchestration/client.js +105 -0
  44. package/lib/api/orchestration/events.d.ts +88 -0
  45. package/lib/api/orchestration/events.js +245 -0
  46. package/lib/api/orchestration/generated.d.ts +693 -0
  47. package/lib/api/orchestration/generated.js +687 -0
  48. package/lib/api/orchestration/index.d.ts +30 -0
  49. package/lib/api/orchestration/index.js +34 -0
  50. package/lib/api/orchestration/lifecycle.d.ts +39 -0
  51. package/lib/api/orchestration/lifecycle.js +57 -0
  52. package/lib/api/orchestration/measures.d.ts +55 -0
  53. package/lib/api/orchestration/measures.js +99 -0
  54. package/lib/api/otel/dashboards.d.ts +56 -0
  55. package/lib/api/otel/dashboards.js +50 -0
  56. package/lib/api/otel/index.d.ts +1 -0
  57. package/lib/api/otel/index.js +1 -0
  58. package/lib/api/otel/metrics.d.ts +16 -0
  59. package/lib/api/otel/metrics.js +15 -0
  60. package/lib/api/scheduler/client.d.ts +32 -0
  61. package/lib/api/scheduler/client.js +42 -0
  62. package/lib/api/scheduler/index.d.ts +8 -0
  63. package/lib/api/scheduler/index.js +16 -0
  64. package/lib/api/scheduler/request.d.ts +28 -0
  65. package/lib/api/scheduler/request.js +50 -0
  66. package/lib/api/scheduler/types.d.ts +88 -0
  67. package/lib/api/scheduler/types.js +5 -0
  68. package/lib/api/spacer/comments.d.ts +97 -0
  69. package/lib/api/spacer/comments.js +45 -0
  70. package/lib/api/spacer/index.d.ts +9 -0
  71. package/lib/api/spacer/index.js +17 -0
  72. package/lib/api/spacer/notebooks.d.ts +33 -0
  73. package/lib/api/spacer/notebooks.js +39 -0
  74. package/lib/api/spacer/request.d.ts +25 -0
  75. package/lib/api/spacer/request.js +46 -0
  76. package/lib/api/spacer/spaces.d.ts +68 -0
  77. package/lib/api/spacer/spaces.js +45 -0
  78. package/lib/api/utils/validation.d.ts +4 -4
  79. package/lib/api/utils/validation.js +10 -7
  80. package/lib/client/auth/strategies.js +1 -1
  81. package/lib/client/constants.d.ts +1 -0
  82. package/lib/client/constants.js +1 -0
  83. package/lib/client/mixins/IAMMixin.js +2 -2
  84. package/lib/components/animation/AnimatedText.d.ts +1 -44
  85. package/lib/components/animation/AnimatedText.js +6 -122
  86. package/lib/components/anonymous/AnonymousKeyExpired.d.ts +56 -0
  87. package/lib/components/anonymous/AnonymousKeyExpired.js +96 -0
  88. package/lib/components/anonymous/AnonymousKeyTimer.d.ts +63 -0
  89. package/lib/components/anonymous/AnonymousKeyTimer.js +140 -0
  90. package/lib/components/anonymous/index.d.ts +7 -0
  91. package/lib/components/anonymous/index.js +15 -0
  92. package/lib/components/avatars/UserAvatar.d.ts +7 -1
  93. package/lib/components/avatars/UserAvatar.js +15 -1
  94. package/lib/components/billing/BillingEntitySelect.d.ts +6 -0
  95. package/lib/components/billing/BillingEntitySelect.js +49 -10
  96. package/lib/components/billing/eligibility.d.ts +22 -0
  97. package/lib/components/billing/eligibility.js +27 -0
  98. package/lib/components/billing/index.d.ts +1 -0
  99. package/lib/components/billing/index.js +1 -0
  100. package/lib/components/checkout/StripeCheckout.d.ts +1 -1
  101. package/lib/components/checkout/StripeCheckout.js +1 -1
  102. package/lib/components/collaboration/LiveEditorCollaborators.d.ts +5 -0
  103. package/lib/components/collaboration/LiveEditorCollaborators.js +3 -1
  104. package/lib/components/display/DatalayerBox.d.ts +2 -3
  105. package/lib/components/display/DatalayerBox.js +1 -1
  106. package/lib/components/display/NavLink.js +3 -2
  107. package/lib/components/display/VisuallyHidden.d.ts +1 -2
  108. package/lib/components/labels/StatusLabels.d.ts +2 -10
  109. package/lib/components/labels/StatusLabels.js +2 -26
  110. package/lib/components/principal/PrincipalAppearance.d.ts +21 -42
  111. package/lib/components/principal/PrincipalAppearance.js +232 -174
  112. package/lib/components/principal/PrincipalAvatar.d.ts +8 -1
  113. package/lib/components/principal/PrincipalAvatar.js +2 -2
  114. package/lib/components/principal/PrincipalDetailsOverlay.d.ts +8 -1
  115. package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
  116. package/lib/components/screencapture/Screencapture.js +46 -3
  117. package/lib/components/sharing/ShareAccessComponent.d.ts +45 -4
  118. package/lib/components/sharing/ShareAccessComponent.js +110 -78
  119. package/lib/components/sharing/SharingEditor.d.ts +5 -1
  120. package/lib/components/sharing/SharingEditor.js +9 -4
  121. package/lib/components/sharing/index.d.ts +1 -0
  122. package/lib/components/sharing/index.js +1 -0
  123. package/lib/components/sharing/sandboxSharing.d.ts +26 -0
  124. package/lib/components/sharing/sandboxSharing.js +47 -0
  125. package/lib/components/spaces/SpaceDetailsOverlay.d.ts +39 -0
  126. package/lib/components/spaces/SpaceDetailsOverlay.js +80 -0
  127. package/lib/components/spaces/SpaceDisplay.d.ts +38 -0
  128. package/lib/components/spaces/SpaceDisplay.js +92 -0
  129. package/lib/components/spaces/index.d.ts +4 -0
  130. package/lib/components/spaces/index.js +7 -0
  131. package/lib/components/spaces/spaceDisplayModel.d.ts +93 -0
  132. package/lib/components/spaces/spaceDisplayModel.js +170 -0
  133. package/lib/config/Configuration.d.ts +4 -4
  134. package/lib/config/Configuration.js +7 -7
  135. package/lib/config/index.d.ts +1 -0
  136. package/lib/config/index.js +1 -0
  137. package/lib/config/planes.d.ts +45 -0
  138. package/lib/config/planes.js +53 -0
  139. package/lib/hooks/cacheConverters.d.ts +0 -7
  140. package/lib/hooks/cacheConverters.js +0 -25
  141. package/lib/hooks/index.d.ts +1 -0
  142. package/lib/hooks/index.js +1 -0
  143. package/lib/hooks/useBackdrop.d.ts +4 -5
  144. package/lib/hooks/useBillingEntityStore.js +3 -0
  145. package/lib/hooks/useCache.d.ts +197 -44
  146. package/lib/hooks/useCache.js +1586 -494
  147. package/lib/hooks/useContents.d.ts +22 -1
  148. package/lib/hooks/useContents.js +90 -21
  149. package/lib/hooks/useMcp.d.ts +87 -1
  150. package/lib/hooks/useMcp.js +248 -13
  151. package/lib/hooks/useNavigate.d.ts +41 -3
  152. package/lib/hooks/useNavigate.js +116 -52
  153. package/lib/hooks/usePrincipalCacheStore.js +3 -0
  154. package/lib/hooks/usePrincipalStore.js +3 -0
  155. package/lib/hooks/useSpaceCacheStore.d.ts +50 -0
  156. package/lib/hooks/useSpaceCacheStore.js +85 -0
  157. package/lib/hooks/useWindowSize.js +1 -1
  158. package/lib/models/CreditsDTO.d.ts +6 -5
  159. package/lib/models/CreditsDTO.js +7 -5
  160. package/lib/models/IAMProvidersSpecs.d.ts +10 -5
  161. package/lib/models/IAMProvidersSpecs.js +0 -23
  162. package/lib/models/McpBinding.d.ts +6 -0
  163. package/lib/models/Organization.d.ts +69 -0
  164. package/lib/models/Organization.js +96 -0
  165. package/lib/models/Secret.d.ts +1 -2
  166. package/lib/models/Secret.js +2 -18
  167. package/lib/models/StartedBy.d.ts +34 -0
  168. package/lib/models/StartedBy.js +36 -0
  169. package/lib/models/Team.d.ts +2 -0
  170. package/lib/models/Team.js +2 -0
  171. package/lib/models/User.d.ts +4 -0
  172. package/lib/models/User.js +3 -0
  173. package/lib/models/UserSettings.d.ts +13 -0
  174. package/lib/models/UserSettings.js +9 -0
  175. package/lib/models/contents/__fixtures__/v1-contracts.json +158 -30
  176. package/lib/navigation/adapters/react-router.d.ts +1 -1
  177. package/lib/navigation/adapters/react-router.js +3 -0
  178. package/lib/navigation/components.js +2 -3
  179. package/lib/routes/publicPaths.js +4 -0
  180. package/lib/state/index.d.ts +1 -0
  181. package/lib/state/index.js +1 -0
  182. package/lib/state/sessionEnd.d.ts +55 -0
  183. package/lib/state/sessionEnd.js +162 -0
  184. package/lib/state/substates/CoreState.js +3 -16
  185. package/lib/state/substates/IAMState.d.ts +3 -3
  186. package/lib/state/substates/IAMState.js +7 -0
  187. package/lib/state/substates/LayoutState.js +12 -0
  188. package/lib/state/substates/NavigationState.d.ts +82 -0
  189. package/lib/state/substates/NavigationState.js +255 -0
  190. package/lib/state/substates/ProfileState.d.ts +1 -0
  191. package/lib/state/substates/ProfileState.js +1 -0
  192. package/lib/state/substates/index.d.ts +1 -0
  193. package/lib/state/substates/index.js +1 -0
  194. package/lib/utils/Lazy.d.ts +20 -0
  195. package/lib/utils/Lazy.js +32 -6
  196. package/lib/utils/Screencapture.d.ts +15 -0
  197. package/lib/utils/Screencapture.js +26 -20
  198. package/lib/utils/WithSuspense.d.ts +11 -1
  199. package/lib/utils/WithSuspense.js +11 -1
  200. package/lib/views/mcp/AdmittedClients.d.ts +44 -0
  201. package/lib/views/mcp/AdmittedClients.js +59 -0
  202. package/lib/views/mcp/ApprovalQueue.d.ts +66 -0
  203. package/lib/views/mcp/ApprovalQueue.js +95 -0
  204. package/lib/views/mcp/ConnectedAgents.js +38 -3
  205. package/lib/views/mcp/EnterpriseConsole.d.ts +1 -1
  206. package/lib/views/mcp/EnterpriseConsole.js +23 -1
  207. package/lib/views/mcp/IdentityProviders.d.ts +33 -0
  208. package/lib/views/mcp/IdentityProviders.js +299 -0
  209. package/lib/views/mcp/McpDashboard.js +90 -7
  210. package/lib/views/mcp/McpHome.js +1 -1
  211. package/lib/views/mcp/McpObservability.d.ts +30 -1
  212. package/lib/views/mcp/McpObservability.js +105 -19
  213. package/lib/views/mcp/NotebookRuns.d.ts +39 -0
  214. package/lib/views/mcp/NotebookRuns.js +44 -0
  215. package/lib/views/mcp/OrganizationPolicy.js +2 -1
  216. package/lib/views/mcp/PersonalPolicy.js +2 -1
  217. package/lib/views/mcp/PolicyForm.d.ts +17 -1
  218. package/lib/views/mcp/PolicyForm.js +25 -3
  219. package/lib/views/mcp/PolicyHistory.d.ts +13 -0
  220. package/lib/views/mcp/PolicyHistory.js +14 -1
  221. package/lib/views/mcp/RunDetail.d.ts +87 -0
  222. package/lib/views/mcp/RunDetail.js +186 -0
  223. package/lib/views/mcp/ScimProvisioning.d.ts +54 -0
  224. package/lib/views/mcp/ScimProvisioning.js +173 -0
  225. package/lib/views/mcp/TeamPolicies.js +66 -5
  226. package/lib/views/mcp/ToolAccess.d.ts +60 -0
  227. package/lib/views/mcp/ToolAccess.js +101 -0
  228. package/lib/views/mcp/TraceTimeline.d.ts +81 -0
  229. package/lib/views/mcp/TraceTimeline.js +132 -0
  230. package/lib/views/mcp/index.d.ts +5 -0
  231. package/lib/views/mcp/index.js +5 -0
  232. package/lib/views/otel/simpleAuthStore.js +3 -0
  233. package/lib/views/secrets/SecretEdit.js +3 -2
  234. package/lib/views/secrets/SecretNew.js +5 -1
  235. package/package.json +271 -264
  236. package/scripts/generate-mcp-types.py +16 -10
  237. package/scripts/generate-orchestration-types.py +920 -0
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Label, Link, Spinner, Text } from '@primer/react';
3
+ import { Box } from '@datalayer/primer-addons';
4
+ import { AlertIcon, CheckCircleIcon, GlobeIcon, QuestionIcon, } from '@primer/octicons-react';
5
+ import { useAdmittedClients } from '../../hooks/useMcp.js';
6
+ const Row = ({ client }) => {
7
+ const unreadable = client.kind === 'url' && client.resolved === false;
8
+ const unusable = client.kind === 'unusable';
9
+ const icon = unusable ? (_jsx(QuestionIcon, {})) : unreadable ? (_jsx(AlertIcon, {})) : client.kind === 'hostname' ? (_jsx(GlobeIcon, {})) : (_jsx(CheckCircleIcon, {}));
10
+ const tone = unusable || unreadable ? 'danger.fg' : 'fg.muted';
11
+ return (_jsxs(Box, { sx: {
12
+ display: 'grid',
13
+ gridTemplateColumns: 'auto 1fr',
14
+ gap: 2,
15
+ alignItems: 'start',
16
+ py: 2,
17
+ borderTop: '1px solid',
18
+ borderColor: 'border.muted',
19
+ }, children: [_jsx(Box, { sx: { color: tone, pt: '2px' }, children: icon }), _jsxs(Box, { sx: { display: 'grid', gap: 1, minWidth: 0 }, children: [_jsxs(Box, { sx: {
20
+ display: 'flex',
21
+ gap: 2,
22
+ alignItems: 'center',
23
+ flexWrap: 'wrap',
24
+ }, children: [client.clientName && (_jsx(Text, { sx: { fontWeight: 'semibold' }, children: client.clientName })), client.kind === 'hostname' && (_jsx(Label, { variant: "accent", size: "small", children: "Whole host" })), client.localhostOnly && (
25
+ // Worth a label of its own: every redirect goes to the reader's
26
+ // own machine, and a document cannot prove which local program
27
+ // is listening on the port.
28
+ _jsx(Label, { variant: "attention", size: "small", children: "Redirects to localhost" })), unreadable && (_jsx(Label, { variant: "danger", size: "small", children: "Cannot be read" })), unusable && (_jsx(Label, { variant: "danger", size: "small", children: "Matches nothing" }))] }), _jsx(Text, { sx: {
29
+ fontSize: 0,
30
+ fontFamily: 'mono',
31
+ color: 'fg.muted',
32
+ overflowWrap: 'anywhere',
33
+ }, children: client.entry }), client.detail && (_jsx(Text, { sx: { fontSize: 0, color: tone }, children: client.detail })), client.clientUri && (_jsx(Link, { href: client.clientUri, target: "_blank", rel: "noreferrer noopener", sx: { fontSize: 0 }, children: client.clientHostname || client.clientUri }))] })] }));
34
+ };
35
+ export const AdmittedClients = ({ scope, subjectUid, edited = false, }) => {
36
+ const described = useAdmittedClients(scope, subjectUid);
37
+ if (described.isPending && !described.data) {
38
+ return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 3 }, children: _jsx(Spinner, { size: "small" }) }));
39
+ }
40
+ // An error here is not worth a blankslate: the field above still works and
41
+ // the policy is unaffected. Saying nothing would be worse than saying it
42
+ // could not be checked, so it says that and stays out of the way.
43
+ if (described.isError) {
44
+ return (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: "The admitted clients could not be described just now. The list above is still what is enforced." }));
45
+ }
46
+ const answer = described.data;
47
+ if (!answer) {
48
+ return null;
49
+ }
50
+ if (!answer.allowlisted) {
51
+ // Said, not rendered as an empty list. An empty allowlist is not an
52
+ // allowlist, and "no clients admitted" would be the opposite of what
53
+ // the gateway does with a blank one.
54
+ return (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: answer.detail ??
55
+ 'This layer names no client allowlist, so every client is admitted by it.' }));
56
+ }
57
+ return (_jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [edited && (_jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'attention.fg', m: 0 }, children: ["Describing the ", _jsx("strong", { children: "saved" }), " list. Save to check what you have just typed."] })), answer.clients.map(client => (_jsx(Row, { client: client }, client.entry)))] }));
58
+ };
59
+ export default AdmittedClients;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The calls an agent cannot make without somebody saying yes.
3
+ *
4
+ * A source set to `explicit` stops every call it does not already hold an
5
+ * approval for. The call waits as `pending-approval`, and the person who
6
+ * owns the source is the only one who can move it — which means that until
7
+ * this page existed, "approval required" was a setting that stopped agents
8
+ * and gave nobody a way to unstop them.
9
+ *
10
+ * **An approval is bound to the arguments it was asked about.** Contents
11
+ * hashes them and re-checks the hash when the call runs, so approving is
12
+ * approving *those* arguments and not that tool in general. That is why the
13
+ * redacted arguments are drawn in full rather than summarised: a reader who
14
+ * cannot see what they are allowing is a reader who approves everything.
15
+ *
16
+ * **A pending approval that has expired is not decidable**, and the buttons
17
+ * go rather than fail. Contents expires them lazily — the record still says
18
+ * `pending` until something looks at it — so a row can arrive here already
19
+ * over, and offering an Approve button on it would be offering a decision
20
+ * the server will refuse.
21
+ *
22
+ * @module views/mcp/ApprovalQueue
23
+ */
24
+ import type { JSX } from 'react';
25
+ import type { McpApproval } from '../../api/contents/generated';
26
+ import { type McpErrorStateFn } from './types';
27
+ /** How each state of an approval is drawn. */
28
+ export declare const APPROVAL_LOOK: Record<McpApproval['status'], {
29
+ label: string;
30
+ variant: 'attention' | 'success' | 'danger' | 'secondary';
31
+ }>;
32
+ /**
33
+ * Whether this approval can still be decided.
34
+ *
35
+ * Two conditions, and the second is the one that matters: the record says
36
+ * `pending`, *and* its deadline has not passed. Contents expires an approval
37
+ * when something next reads it rather than on a timer, so a row fetched a
38
+ * minute ago can be `pending` and over. Trusting the status alone would draw
39
+ * buttons whose only outcome is an error.
40
+ *
41
+ * An unparseable deadline is treated as passed. That is the safe direction:
42
+ * the worst case is somebody has to ask the agent to try again, where the
43
+ * other way round is a decision taken on a record nobody can date.
44
+ */
45
+ export declare const isDecidable: (approval: Pick<McpApproval, "status" | "expiresAt">, now?: number) => boolean;
46
+ /**
47
+ * What a row says about its own deadline.
48
+ *
49
+ * A pending approval that has run out is drawn as expired even though the
50
+ * record still says pending — the reader's question is "can I still act on
51
+ * this", and the record's answer to that is stale by construction.
52
+ */
53
+ export declare const approvalLook: (approval: Pick<McpApproval, "status" | "expiresAt">, now?: number) => {
54
+ label: string;
55
+ variant: "attention" | "success" | "danger" | "secondary";
56
+ };
57
+ /** The states this page can be filtered to. `pending` is what it opens on. */
58
+ export declare const APPROVAL_FILTERS: McpApproval['status'][];
59
+ export interface ApprovalQueueProps {
60
+ /** The application's words for a failed request. */
61
+ errorState: McpErrorStateFn;
62
+ /** Only the approvals of one source, when the caller has one in mind. */
63
+ sourceUid?: string;
64
+ }
65
+ export declare const ApprovalQueue: ({ errorState, sourceUid, }: ApprovalQueueProps) => JSX.Element;
66
+ export default ApprovalQueue;
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Button, Heading, Label, RelativeTime, SegmentedControl, Spinner, Text, Textarea, } from '@primer/react';
4
+ import { Box } from '@datalayer/primer-addons';
5
+ import { McpErrorBlankslate } from '../../components/mcp/index.js';
6
+ import { useDecideMcpApproval, useMcpApprovals, } from '../../hooks/useContents.js';
7
+ /** How each state of an approval is drawn. */
8
+ export const APPROVAL_LOOK = {
9
+ pending: { label: 'Waiting on you', variant: 'attention' },
10
+ approved: { label: 'Approved', variant: 'success' },
11
+ rejected: { label: 'Rejected', variant: 'danger' },
12
+ expired: { label: 'Expired', variant: 'secondary' },
13
+ consumed: { label: 'Used', variant: 'secondary' },
14
+ };
15
+ /**
16
+ * Whether this approval can still be decided.
17
+ *
18
+ * Two conditions, and the second is the one that matters: the record says
19
+ * `pending`, *and* its deadline has not passed. Contents expires an approval
20
+ * when something next reads it rather than on a timer, so a row fetched a
21
+ * minute ago can be `pending` and over. Trusting the status alone would draw
22
+ * buttons whose only outcome is an error.
23
+ *
24
+ * An unparseable deadline is treated as passed. That is the safe direction:
25
+ * the worst case is somebody has to ask the agent to try again, where the
26
+ * other way round is a decision taken on a record nobody can date.
27
+ */
28
+ export const isDecidable = (approval, now = Date.now()) => {
29
+ if (approval.status !== 'pending') {
30
+ return false;
31
+ }
32
+ const deadline = Date.parse(approval.expiresAt);
33
+ return Number.isFinite(deadline) && deadline > now;
34
+ };
35
+ /**
36
+ * What a row says about its own deadline.
37
+ *
38
+ * A pending approval that has run out is drawn as expired even though the
39
+ * record still says pending — the reader's question is "can I still act on
40
+ * this", and the record's answer to that is stale by construction.
41
+ */
42
+ export const approvalLook = (approval, now = Date.now()) => approval.status === 'pending' && !isDecidable(approval, now)
43
+ ? APPROVAL_LOOK.expired
44
+ : APPROVAL_LOOK[approval.status];
45
+ /** The states this page can be filtered to. `pending` is what it opens on. */
46
+ export const APPROVAL_FILTERS = [
47
+ 'pending',
48
+ 'approved',
49
+ 'rejected',
50
+ ];
51
+ export const ApprovalQueue = ({ errorState, sourceUid, }) => {
52
+ const [status, setStatus] = useState('pending');
53
+ const [noteFor, setNoteFor] = useState({});
54
+ const approvals = useMcpApprovals(status, sourceUid ? { sourceUid } : {});
55
+ const decide = useDecideMcpApproval();
56
+ const items = approvals.data?.items ?? [];
57
+ return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: "Approvals" }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: "A source set to require approval stops each call until you allow it. An approval covers the exact arguments below and nothing else, so a second call with different arguments asks again." })] }), _jsx(SegmentedControl, { "aria-label": "Which approvals", size: "small", children: APPROVAL_FILTERS.map(state => (_jsx(SegmentedControl.Button, { selected: status === state, onClick: () => setStatus(state), children: APPROVAL_LOOK[state].label }, state))) }), approvals.isError ? (_jsx(McpErrorBlankslate, { state: errorState(approvals.error, 'The approvals'), onRetry: () => approvals.refetch() })) : approvals.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: status === 'pending'
58
+ ? 'Nothing is waiting on you.'
59
+ : `No ${APPROVAL_LOOK[status].label.toLowerCase()} approvals.` })) : (items.map(approval => {
60
+ const look = approvalLook(approval);
61
+ const decidable = isDecidable(approval);
62
+ return (_jsxs(Box, { sx: {
63
+ border: '1px solid',
64
+ borderColor: decidable ? 'attention.emphasis' : 'border.default',
65
+ borderRadius: 2,
66
+ p: 3,
67
+ display: 'grid',
68
+ gap: 2,
69
+ minWidth: 0,
70
+ }, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: approval.tool }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: approval.actorUid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["asked ", _jsx(RelativeTime, { datetime: approval.createdAt })] }), decidable && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["expires ", _jsx(RelativeTime, { datetime: approval.expiresAt })] }))] }), _jsx(Box, { as: "pre", sx: {
71
+ fontFamily: 'mono',
72
+ fontSize: 0,
73
+ whiteSpace: 'pre-wrap',
74
+ overflowX: 'auto',
75
+ bg: 'canvas.subtle',
76
+ borderRadius: 2,
77
+ p: 2,
78
+ m: 0,
79
+ }, children: JSON.stringify(approval.argumentsRedacted, null, 2) }), approval.destinationUri && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Writes to", ' ', _jsx(Text, { as: "span", sx: { fontFamily: 'mono' }, children: approval.destinationUri })] })), decidable ? (_jsxs(_Fragment, { children: [_jsx(Textarea, { "aria-label": `Why, for ${approval.tool}`, rows: 2, placeholder: "Why (optional; kept with the decision)", value: noteFor[approval.uid] ?? '', onChange: event => setNoteFor(current => ({
80
+ ...current,
81
+ [approval.uid]: event.target.value,
82
+ })) }), _jsxs(Box, { sx: { display: 'flex', gap: 2 }, children: [_jsx(Button, { variant: "primary", disabled: decide.isPending, onClick: () => decide.mutate({
83
+ approvalUid: approval.uid,
84
+ decision: 'approve',
85
+ note: noteFor[approval.uid] || undefined,
86
+ }), children: "Allow this call" }), _jsx(Button, { variant: "danger", disabled: decide.isPending, onClick: () => decide.mutate({
87
+ approvalUid: approval.uid,
88
+ decision: 'reject',
89
+ note: noteFor[approval.uid] || undefined,
90
+ }), children: "Refuse" })] })] })) : (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [approval.decidedBy
91
+ ? `Decided by ${approval.decidedBy}`
92
+ : 'This request ran out before anybody decided it. The agent has to ask again.', approval.note ? ` — ${approval.note}` : ''] }))] }, approval.uid));
93
+ }))] }));
94
+ };
95
+ export default ApprovalQueue;
@@ -5,19 +5,37 @@ import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimenta
5
5
  import { Box } from '@datalayer/primer-addons';
6
6
  import { KebabHorizontalIcon, PlugIcon } from '@primer/octicons-react';
7
7
  import { ClientBadge, McpErrorBlankslate, ScopeList, } from '../../components/mcp/index.js';
8
- import { useConnectedAgents, useDisconnectAgent } from '../../hooks/useMcp.js';
8
+ import { useConnectedAgents, useDisconnectAgent, useDisconnectEveryAgent, } from '../../hooks/useMcp.js';
9
9
  import { useNavigate, useToast } from '../../hooks/index.js';
10
10
  export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly = false, }) => {
11
11
  const navigate = useNavigate();
12
12
  const { enqueueToast } = useToast();
13
13
  const agents = useConnectedAgents();
14
14
  const disconnect = useDisconnectAgent();
15
+ const disconnectEvery = useDisconnectEveryAgent();
15
16
  const [disconnecting, setDisconnecting] = useState(null);
17
+ const [disconnectingEvery, setDisconnectingEvery] = useState(false);
16
18
  // Primer 37 types Dialog's focus refs with React 18's non-nullable
17
19
  // `RefObject<HTMLElement>`. React 19's `useRef(null)` is nullable, and this
18
20
  // ref is only handed to Dialog, so narrow it once here.
19
21
  const returnFocusRef = useRef(null);
20
22
  const rows = useMemo(() => (agents.data ?? []).map(agent => ({ ...agent, id: agent.uid })), [agents.data]);
23
+ const confirmDisconnectEvery = () => {
24
+ disconnectEvery.mutate(undefined, {
25
+ onSuccess: answer => {
26
+ enqueueToast(answer.revoked === 1
27
+ ? '1 agent disconnected.'
28
+ : `${answer.revoked} agents disconnected.`, { variant: 'success' });
29
+ setDisconnectingEvery(false);
30
+ },
31
+ onError: error => {
32
+ setDisconnectingEvery(false);
33
+ enqueueToast(`Could not disconnect: ${error.message}`, {
34
+ variant: 'error',
35
+ });
36
+ },
37
+ });
38
+ };
21
39
  const confirmDisconnect = () => {
22
40
  const agent = disconnecting;
23
41
  if (!agent) {
@@ -83,7 +101,12 @@ export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly
83
101
  if (agents.isError) {
84
102
  return (_jsx(McpErrorBlankslate, { state: errorState(agents.error, 'Connected agents'), onRetry: () => agents.refetch() }));
85
103
  }
86
- return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Connected Agents" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Every agent you have authorized, what it may do, and when it last did it." })] })), 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: "connected-agents", children: "Agents" }), _jsx(Table.Subtitle, { as: "p", id: "connected-agents-subtitle", children: "An agent acts with exactly the access of the person who authorized it, and no more." }), _jsx(DataTable, { "aria-labelledby": "connected-agents", "aria-describedby": "connected-agents-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PlugIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent connected" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Connect an MCP client and approve the scopes it asks for; the grant appears here and can be revoked from here." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.access), children: "Connect an agent" })] })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "Narrowing a grant to fewer scopes without disconnecting arrives with the organization policy." }), disconnecting && (_jsx(Dialog, { title: "Disconnect this agent?", onClose: () => setDisconnecting(null), returnFocusRef: returnFocusRef, footerButtons: [
104
+ return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { sx: {
105
+ display: 'flex',
106
+ alignItems: 'start',
107
+ justifyContent: 'space-between',
108
+ gap: 3,
109
+ }, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Connected Agents" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Every agent you have authorized, what it may do, and when it last did it." })] }), !readOnly && rows.length > 0 && (_jsx(Button, { variant: "danger", onClick: () => setDisconnectingEvery(true), disabled: disconnectEvery.isPending, children: "Disconnect all" }))] })), 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: "connected-agents", children: "Agents" }), _jsx(Table.Subtitle, { as: "p", id: "connected-agents-subtitle", children: "An agent acts with exactly the access of the person who authorized it, and no more." }), _jsx(DataTable, { "aria-labelledby": "connected-agents", "aria-describedby": "connected-agents-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PlugIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent connected" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Connect an MCP client and approve the scopes it asks for; the grant appears here and can be revoked from here." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.access), children: "Connect an agent" })] })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "Narrowing a grant to fewer scopes without disconnecting arrives with the organization policy." }), disconnecting && (_jsx(Dialog, { title: "Disconnect this agent?", onClose: () => setDisconnecting(null), returnFocusRef: returnFocusRef, footerButtons: [
87
110
  {
88
111
  buttonType: 'default',
89
112
  content: 'Keep it',
@@ -95,6 +118,18 @@ export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly
95
118
  onClick: confirmDisconnect,
96
119
  disabled: disconnect.isPending,
97
120
  },
98
- ], children: _jsxs(Text, { sx: { fontSize: 1 }, children: [disconnecting.clientName || disconnecting.clientId, " loses its grant at once and its next call is refused. An access token it still holds dies with its own short expiry. The other agents are untouched, and nothing this one already did is undone."] }) }))] }));
121
+ ], children: _jsxs(Text, { sx: { fontSize: 1 }, children: [disconnecting.clientName || disconnecting.clientId, " loses its grant at once and its next call is refused. An access token it still holds dies with its own short expiry. The other agents are untouched, and nothing this one already did is undone."] }) })), disconnectingEvery && (_jsxs(Dialog, { title: "Disconnect every agent?", onClose: () => setDisconnectingEvery(false), returnFocusRef: returnFocusRef, footerButtons: [
122
+ {
123
+ buttonType: 'default',
124
+ content: 'Keep them',
125
+ onClick: () => setDisconnectingEvery(false),
126
+ },
127
+ {
128
+ buttonType: 'danger',
129
+ content: 'Disconnect all',
130
+ onClick: confirmDisconnectEvery,
131
+ disabled: disconnectEvery.isPending,
132
+ },
133
+ ], children: [_jsx(Text, { sx: { fontSize: 1 }, children: "Every agent you have authorized loses its grant at once \u2014 signing out everywhere. Each will ask again the next time you connect it, and this time it goes through the approval screen rather than being answered from what you agreed before, so this is also how you force a fresh approval." }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2, mb: 0 }, children: "Yours only. Nobody else\u2019s sessions end, in this organization or any other, and nothing any agent already did is undone." })] }))] }));
99
134
  };
100
135
  export default ConnectedAgents;
@@ -34,7 +34,7 @@ import type { McpAuditFilters } from '../../api/mcp';
34
34
  import { type McpObservabilityPane } from './McpObservability';
35
35
  import { type McpErrorStateFn, type McpRoutes } from './types';
36
36
  /** The pages milestone 1 carries. */
37
- export type EnterpriseConsolePage = 'overview' | 'usage' | 'agents' | 'service-agents' | 'policy' | 'teams' | 'alerts' | 'audit' | 'observability';
37
+ export type EnterpriseConsolePage = 'overview' | 'usage' | 'agents' | 'service-agents' | 'policy' | 'teams' | 'identity-providers' | 'alerts' | 'approvals' | 'audit' | 'observability';
38
38
  export declare const ENTERPRISE_CONSOLE_PAGES: {
39
39
  id: EnterpriseConsolePage;
40
40
  label: string;
@@ -9,11 +9,14 @@ import { useNavigate, useToast } from '../../hooks/index.js';
9
9
  import { AuditLog } from './AuditLog.js';
10
10
  import { McpObservability, } from './McpObservability.js';
11
11
  import { AlertRules } from './AlertRules.js';
12
+ import { IdentityProviders } from './IdentityProviders.js';
13
+ import { ScimProvisioning } from './ScimProvisioning.js';
12
14
  import { TeamPolicies } from './TeamPolicies.js';
13
15
  import { OrganizationUsage } from './OrganizationUsage.js';
14
16
  import { OrganizationPolicy } from './OrganizationPolicy.js';
15
17
  import { ServiceAgents } from './ServiceAgents.js';
16
18
  import { clientStatusOf, plural, timeAgo } from './format.js';
19
+ import { ApprovalQueue } from './ApprovalQueue.js';
17
20
  export const ENTERPRISE_CONSOLE_PAGES = [
18
21
  { id: 'overview', label: 'Overview' },
19
22
  { id: 'usage', label: 'Usage' },
@@ -21,7 +24,9 @@ export const ENTERPRISE_CONSOLE_PAGES = [
21
24
  { id: 'service-agents', label: 'Service Agents' },
22
25
  { id: 'policy', label: 'Policy' },
23
26
  { id: 'teams', label: 'Teams' },
27
+ { id: 'identity-providers', label: 'Identity' },
24
28
  { id: 'alerts', label: 'Alerts' },
29
+ { id: 'approvals', label: 'Approvals' },
25
30
  { id: 'audit', label: 'Audit' },
26
31
  { id: 'observability', label: 'Observability' },
27
32
  ];
@@ -35,7 +40,12 @@ export const pagesForRoles = (roles) => {
35
40
  'service-agents',
36
41
  'policy',
37
42
  'teams',
43
+ // Who may sign in here at all, and as what, is the organization's own
44
+ // authority — the same reason a provider needs an owner to register,
45
+ // never a security auditor or a usage reviewer.
46
+ 'identity-providers',
38
47
  'alerts',
48
+ 'approvals',
39
49
  'audit',
40
50
  'observability',
41
51
  ];
@@ -53,9 +63,21 @@ export const pagesForRoles = (roles) => {
53
63
  // The team layers too: an auditor asked why one team's agents are
54
64
  // treated differently needs the layer that treats them so.
55
65
  'teams',
66
+ // Identity providers too, read-only: which directory an organization
67
+ // trusts and what its group mapping grants is exactly the posture an
68
+ // auditor is there to read, and the page carries no secret — the
69
+ // client secret is a reference, never the value.
70
+ 'identity-providers',
56
71
  // The alerts too: what an organization watches for is part of the
57
72
  // security posture an auditor is there to read.
58
73
  'alerts',
74
+ // The approvals are deliberately NOT here. Contents scopes
75
+ // `/mcp-approvals` to the caller — the actor, or the person who owns
76
+ // the source and must decide — so an auditor would open a queue of
77
+ // their own approvals under an organization's console and read it as
78
+ // that organization having none. An empty table that looks like an
79
+ // answer is worse than no tab. What an auditor needs is the decision
80
+ // *after* it was taken, and that is an audit row.
59
81
  'audit',
60
82
  'observability',
61
83
  ];
@@ -167,7 +189,7 @@ export const EnterpriseConsole = ({ errorState, routes, organization, roles, pag
167
189
  : 'neutral' })] }), (overview.data?.refusals ?? []).length > 0 && (_jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1 }, children: "Why calls were refused" }), _jsx(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: (overview.data?.refusals ?? []).map(row => (_jsxs(Label, { size: "small", variant: "danger", children: [row.reason, " \u00B7 ", row.count] }, row.reason))) })] })), _jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1 }, children: "Compliance" }), (overview.data?.compliance ?? []).length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Nothing to check yet: the strip fills as the organization's sign-in, admitted clients and audit export are configured." })) : (_jsx(Box, { sx: { display: 'grid', gap: 1 }, children: (overview.data?.compliance ?? []).map(check => (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Box, { sx: {
168
190
  color: check.ok ? 'success.fg' : 'attention.fg',
169
191
  display: 'flex',
170
- }, children: check.ok ? (_jsx(CheckCircleIcon, { size: 14 })) : (_jsx(AlertIcon, { size: 14 })) }), _jsx(Text, { sx: { fontSize: 1 }, children: check.name }), !check.ok && check.detail && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: check.detail }))] }, check.name))) }))] }), _jsxs(Box, { sx: { display: 'flex', gap: 3, flexWrap: 'wrap' }, children: [_jsx(Metric, { label: "Approvals waiting", value: overview.data?.approvalsWaiting ?? '—', note: "The queue arrives with durable execution" }), _jsx(Metric, { label: "Alerts firing", value: overview.data?.alertsFiring ?? '—', note: "Alert rules arrive with quotas" })] })] }))), current === 'usage' && (_jsx(OrganizationUsage, { errorState: errorState, orgUid: organization.uid, showTitle: false })), current === 'teams' && (_jsx(TeamPolicies, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'alerts' && (_jsx(AlertRules, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'policy' && (_jsx(OrganizationPolicy, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'service-agents' && (_jsx(ServiceAgents, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'agents' &&
192
+ }, children: check.ok ? (_jsx(CheckCircleIcon, { size: 14 })) : (_jsx(AlertIcon, { size: 14 })) }), _jsx(Text, { sx: { fontSize: 1 }, children: check.name }), !check.ok && check.detail && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: check.detail }))] }, check.name))) }))] }), _jsxs(Box, { sx: { display: 'flex', gap: 3, flexWrap: 'wrap' }, children: [_jsx(Metric, { label: "Approvals waiting", value: overview.data?.approvalsWaiting ?? '—', note: "The queue arrives with durable execution" }), _jsx(Metric, { label: "Alerts firing", value: overview.data?.alertsFiring ?? '—', note: "Alert rules arrive with quotas" })] })] }))), current === 'usage' && (_jsx(OrganizationUsage, { errorState: errorState, orgUid: organization.uid, showTitle: false })), current === 'teams' && (_jsx(TeamPolicies, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'identity-providers' && (_jsxs(Box, { sx: { display: 'grid', gap: 5, minWidth: 0 }, children: [_jsx(IdentityProviders, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') }), _jsx(ScimProvisioning, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })] })), current === 'alerts' && (_jsx(AlertRules, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'approvals' && _jsx(ApprovalQueue, { errorState: errorState }), current === 'policy' && (_jsx(OrganizationPolicy, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'service-agents' && (_jsx(ServiceAgents, { errorState: errorState, orgUid: organization.uid, readOnly: !roles.includes('organization_owner') })), current === 'agents' &&
171
193
  (activity.isError ? (_jsx(McpErrorBlankslate, { state: errorState(activity.error, "The organization's agents"), onRetry: () => activity.refetch() })) : activity.isPending && !activity.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 6 }, children: _jsx(Spinner, {}) })) : agentRows.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PlugIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent acts for this organization" }), _jsx(Blankslate.Description, { children: _jsxs(Text, { sx: { textAlign: 'center' }, children: ["A member who authorizes a client while acting for", ' ', organization.name || organization.handle, " appears here with the scopes the grant carries and what it last did."] }) })] })) : (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h4", id: "org-agents", children: "Agents" }), _jsxs(Table.Subtitle, { as: "p", id: "org-agents-subtitle", children: [plural(agentRows.length, 'principal'), " acting through MCP for this organization. A member's own grant is revoked by that member; revoking on their behalf arrives with the organization's agent directory."] }), _jsx(DataTable, { "aria-labelledby": "org-agents", "aria-describedby": "org-agents-subtitle", data: agentRows, columns: agentColumns })] }))), current === 'audit' && (_jsx(AuditLog, { errorState: errorState, routes: routes, showTitle: false, subject: organization.name || organization.handle, filters: { ...auditFilters, org: organization.uid }, onFiltersChange: filters => onAuditFiltersChange({ ...filters, org: undefined }) })), current === 'observability' && (_jsx(McpObservability, { errorState: errorState, routes: routes, showTitle: false, org: organization.uid, pane: observabilityPane, onPaneChange: onObservabilityPaneChange, taskUid: observabilityTask, traceId: observabilityTrace, onSelect: onObservabilitySelect })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "Teams, Policies, Quotas, Alerts, Approvals and Runs join this console as the layers behind them ship. Identity \u2014 providers, group mapping, SCIM and session controls \u2014 comes last, with enterprise sign-in." })] }));
172
194
  };
173
195
  export default EnterpriseConsole;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * An organization's own identity provider.
3
+ *
4
+ * Enterprise sign-in used to be one Okta for the whole deployment. Here an
5
+ * organization registers the directory it already has — issuer, client id,
6
+ * the email domains it claims — and a sign-in with an address at one of
7
+ * those domains is routed to it.
8
+ *
9
+ * **A claim is not proof, and this page is careful to say so.** A domain
10
+ * just typed in and one proved by DNS are shown differently on purpose: a
11
+ * grey "claimed" label beside a domain that routes nothing yet, a green
12
+ * "verified" one beside a domain that does. Collapsing the two into one list
13
+ * would let an owner believe a claim is already live when it is not — and
14
+ * until it is verified, nobody signs in through it at all.
15
+ *
16
+ * **The client secret never appears here**, only its name in the
17
+ * organization's own secret store. This page has no field that could render
18
+ * one, by construction rather than by care taken while writing it.
19
+ *
20
+ * @module views/mcp/IdentityProviders
21
+ */
22
+ import type { JSX } from 'react';
23
+ import type { McpErrorStateFn } from './types';
24
+ export interface IdentityProvidersProps {
25
+ errorState: McpErrorStateFn;
26
+ orgUid: string;
27
+ /** Read-only for anybody who is not an owner: registering a provider
28
+ * decides who may get into the organization at all. */
29
+ readOnly?: boolean;
30
+ showTitle?: boolean;
31
+ }
32
+ export declare const IdentityProviders: ({ errorState, orgUid, readOnly, showTitle, }: IdentityProvidersProps) => JSX.Element;
33
+ export default IdentityProviders;