@datalayer/core 1.1.60 → 1.1.66
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 +2 -2
- package/lib/api/DatalayerApi.d.ts +42 -1
- package/lib/api/DatalayerApi.js +90 -3
- package/lib/api/constants.d.ts +18 -0
- package/lib/api/constants.js +18 -0
- package/lib/api/contents/attachments.d.ts +9 -0
- package/lib/api/contents/attachments.js +43 -0
- package/lib/api/contents/bridges.d.ts +22 -0
- package/lib/api/contents/bridges.js +79 -0
- package/lib/api/contents/capabilities.d.ts +11 -0
- package/lib/api/contents/capabilities.js +21 -0
- package/lib/api/contents/cloudStorage.d.ts +31 -0
- package/lib/api/contents/cloudStorage.js +89 -0
- package/lib/api/contents/dataservers.d.ts +37 -0
- package/lib/api/contents/dataservers.js +121 -0
- package/lib/api/contents/datasets.d.ts +15 -0
- package/lib/api/contents/datasets.js +45 -0
- package/lib/api/contents/datasources.d.ts +70 -0
- package/lib/api/contents/datasources.js +149 -0
- package/lib/api/contents/generated.d.ts +1070 -0
- package/lib/api/contents/index.d.ts +16 -0
- package/lib/api/contents/index.js +20 -0
- package/lib/api/contents/mcp.d.ts +46 -0
- package/lib/api/contents/mcp.js +157 -0
- package/lib/api/contents/objects.d.ts +31 -0
- package/lib/api/contents/objects.js +88 -0
- package/lib/api/contents/operations.d.ts +3 -0
- package/lib/api/contents/operations.js +19 -0
- package/lib/api/contents/runtimeMounts.d.ts +40 -0
- package/lib/api/contents/runtimeMounts.js +59 -0
- package/lib/api/contents/sandboxUid.d.ts +53 -0
- package/lib/api/contents/sandboxUid.js +78 -0
- package/lib/api/contents/sources.d.ts +19 -0
- package/lib/api/contents/sources.js +119 -0
- package/lib/api/contents/sync.d.ts +18 -0
- package/lib/api/contents/sync.js +69 -0
- package/lib/api/contents/transfers.d.ts +38 -0
- package/lib/api/contents/transfers.js +123 -0
- package/lib/api/iam/connectedAgents.d.ts +39 -0
- package/lib/api/iam/connectedAgents.js +52 -0
- package/lib/api/iam/index.d.ts +2 -1
- package/lib/api/iam/index.js +2 -1
- package/lib/api/iam/mcpAlertRules.d.ts +51 -0
- package/lib/api/iam/mcpAlertRules.js +198 -0
- package/lib/api/iam/mcpAuditSettings.d.ts +46 -0
- package/lib/api/iam/mcpAuditSettings.js +120 -0
- package/lib/api/iam/mcpPolicy.d.ts +80 -0
- package/lib/api/iam/mcpPolicy.js +132 -0
- package/lib/api/iam/organizationTeams.d.ts +13 -0
- package/lib/api/iam/organizationTeams.js +43 -0
- package/lib/api/iam/serviceAgents.d.ts +68 -0
- package/lib/api/iam/serviceAgents.js +119 -0
- package/lib/api/index.d.ts +4 -1
- package/lib/api/index.js +3 -1
- package/lib/api/mcp/activity.d.ts +37 -0
- package/lib/api/mcp/activity.js +26 -0
- package/lib/api/mcp/alerts.d.ts +25 -0
- package/lib/api/mcp/alerts.js +51 -0
- package/lib/api/mcp/audit.d.ts +29 -0
- package/lib/api/mcp/audit.js +55 -0
- package/lib/api/mcp/bindings.d.ts +18 -0
- package/lib/api/mcp/bindings.js +42 -0
- package/lib/api/mcp/forwarding.d.ts +25 -0
- package/lib/api/mcp/forwarding.js +46 -0
- package/lib/api/mcp/gateway.d.ts +16 -0
- package/lib/api/mcp/gateway.js +70 -0
- package/lib/api/mcp/generated.d.ts +22 -0
- package/lib/api/mcp/generated.js +213 -0
- package/lib/api/mcp/index.d.ts +17 -0
- package/lib/api/mcp/index.js +25 -0
- package/lib/api/mcp/observability.d.ts +125 -0
- package/lib/api/mcp/observability.js +254 -0
- package/lib/api/mcp/operations.d.ts +83 -0
- package/lib/api/mcp/operations.js +50 -0
- package/lib/api/mcp/organizations.d.ts +102 -0
- package/lib/api/mcp/organizations.js +46 -0
- package/lib/api/mcp/policy.d.ts +9 -0
- package/lib/api/mcp/policy.js +23 -0
- package/lib/api/mcp/tasks.d.ts +49 -0
- package/lib/api/mcp/tasks.js +147 -0
- package/lib/api/utils/validation.d.ts +1 -1
- package/lib/api/utils/validation.js +1 -1
- package/lib/client/auth/index.js +0 -5
- package/lib/client/auth/storage.js +0 -5
- package/lib/client/auth/strategies.js +0 -5
- package/lib/client/base.d.ts +6 -0
- package/lib/client/base.js +8 -0
- package/lib/client/index.d.ts +0 -10
- package/lib/client/index.js +2 -2
- package/lib/client/mixins/ContentsMixin.d.ts +71 -0
- package/lib/client/mixins/ContentsMixin.js +157 -0
- package/lib/client/mixins/IAMMixin.d.ts +0 -31
- package/lib/client/mixins/IAMMixin.js +0 -58
- package/lib/components/animation/AnimatedText.d.ts +1 -0
- package/lib/components/animation/AnimatedText.js +0 -4
- package/lib/components/auth/SigInAPIKey.d.ts +1 -0
- package/lib/components/auth/SigInAPIKey.js +0 -6
- package/lib/components/auth/SignIn.d.ts +1 -0
- package/lib/components/auth/SignIn.js +0 -4
- package/lib/components/auth/SignIn.stories.d.ts +2 -2
- package/lib/components/avatars/BoringAvatar.d.ts +1 -1
- package/lib/components/avatars/BoringAvatar.stories.d.ts +1 -1
- package/lib/components/avatars/DLAvatar.d.ts +1 -1
- package/lib/components/avatars/DLAvatar.js +0 -4
- package/lib/components/avatars/UserAvatar.d.ts +9 -0
- package/lib/components/avatars/UserAvatar.js +0 -12
- package/lib/components/avatars/UserProfileAvatar.d.ts +1 -1
- package/lib/components/banners/NoAutomationBanner.d.ts +1 -1
- package/lib/components/billing/BillingEntitySelect.d.ts +10 -0
- package/lib/components/billing/BillingEntitySelect.js +0 -17
- package/lib/components/billing/index.js +0 -4
- package/lib/components/buttons/DownloadCSVButton.d.ts +1 -1
- package/lib/components/buttons/DownloadJsonButton.d.ts +1 -1
- package/lib/components/buttons/LongActionButton.d.ts +1 -0
- package/lib/components/buttons/LongActionButton.js +0 -4
- package/lib/components/buttons/UploadButton.d.ts +1 -0
- package/lib/components/buttons/UploadButton.js +0 -4
- package/lib/components/checkout/StripeCheckout.d.ts +1 -1
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +31 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +86 -53
- package/lib/components/collaboration/index.js +0 -4
- package/lib/components/confetti/ConfettiSuccess.d.ts +1 -1
- package/lib/components/contents/ContentSourceCard.d.ts +8 -0
- package/lib/components/contents/ContentSourceCard.js +23 -0
- package/lib/components/contents/HomeFolderObjectsBrowser.d.ts +10 -0
- package/lib/components/contents/HomeFolderObjectsBrowser.js +148 -0
- package/lib/components/contents/index.d.ts +2 -0
- package/lib/components/contents/index.js +6 -0
- package/lib/components/context/OrganizationSelect.d.ts +1 -1
- package/lib/components/display/AvatarSkeleton.d.ts +1 -1
- package/lib/components/display/BusyDots.d.ts +1 -1
- package/lib/components/display/CenteredSpinner.d.ts +1 -1
- package/lib/components/display/CodePreview.d.ts +1 -1
- package/lib/components/display/DatalayerBox.d.ts +7 -2
- package/lib/components/display/DatalayerBox.js +6 -6
- package/lib/components/display/HorizontalCenter.d.ts +1 -1
- package/lib/components/display/JupyterDialog.d.ts +1 -0
- package/lib/components/display/JupyterDialog.js +1 -7
- package/lib/components/display/Markdown.d.ts +1 -0
- package/lib/components/display/NotebookSkeleton.d.ts +1 -1
- package/lib/components/display/Placeholder.d.ts +1 -1
- package/lib/components/display/ToTopBranded.d.ts +1 -1
- package/lib/components/echarts/EChartsReact.d.ts +1 -1
- package/lib/components/flashes/FlashClosable.d.ts +1 -0
- package/lib/components/flashes/FlashClosable.js +0 -4
- package/lib/components/flashes/FlashGuest.d.ts +1 -1
- package/lib/components/flashes/FlashSurveys.d.ts +1 -1
- package/lib/components/flashes/FlashUnauthorized.d.ts +1 -0
- package/lib/components/flashes/FlashUnauthorized.js +0 -6
- package/lib/components/flashes/surveys/Survey2025_1.d.ts +1 -1
- package/lib/components/iam/ExternalTokenSilentLogin.d.ts +1 -1
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/components/labels/StatusLabels.d.ts +1 -0
- package/lib/components/labels/VisibilityLabel.d.ts +1 -1
- package/lib/components/landings/StepBlock.d.ts +1 -1
- package/lib/components/mcp/ClientBadge.d.ts +41 -0
- package/lib/components/mcp/ClientBadge.js +33 -0
- package/lib/components/mcp/McpErrorBlankslate.d.ts +21 -0
- package/lib/components/mcp/McpErrorBlankslate.js +17 -0
- package/lib/components/mcp/ScopeList.d.ts +28 -0
- package/lib/components/mcp/ScopeList.js +14 -0
- package/lib/components/mcp/index.d.ts +10 -0
- package/lib/components/mcp/index.js +18 -0
- package/lib/components/navbar/NavigationVisbilityObserver.d.ts +2 -1
- package/lib/components/navbar/SubdomainNavBar.d.ts +10 -5
- package/lib/components/navbar/SubdomainNavBar.js +2 -1
- package/lib/components/notebooks/JupyterNotebook.d.ts +1 -1
- package/lib/components/notebooks/JupyterNotebookToolbar.d.ts +1 -1
- package/lib/components/principal/PrincipalAppearance.d.ts +1 -0
- package/lib/components/principal/PrincipalAppearance.js +0 -8
- package/lib/components/principal/PrincipalAvatar.d.ts +1 -0
- package/lib/components/principal/PrincipalAvatar.js +17 -9
- package/lib/components/principal/PrincipalBadge.d.ts +1 -1
- package/lib/components/principal/PrincipalBanner.d.ts +1 -1
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +31 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +64 -33
- package/lib/components/principal/PrincipalHoverCard.d.ts +31 -0
- package/lib/components/principal/PrincipalHoverCard.js +52 -0
- package/lib/components/principal/PrincipalSwitcherMenu.d.ts +1 -0
- package/lib/components/principal/PrincipalSwitcherMenu.js +5 -14
- package/lib/components/principal/index.d.ts +2 -1
- package/lib/components/principal/index.js +2 -1
- package/lib/components/progress/ConsumptionBar.d.ts +1 -0
- package/lib/components/progress/ConsumptionBar.js +0 -4
- package/lib/components/progress/ProgressRing.d.ts +1 -0
- package/lib/components/progress/SpinnerBar.d.ts +1 -1
- package/lib/components/progress/Timer.d.ts +1 -0
- package/lib/components/progress/Timer.js +0 -4
- package/lib/components/screencapture/ScreencaptureButton.d.ts +1 -1
- package/lib/components/sharing/ShareAccessComponent.d.ts +15 -1
- package/lib/components/sharing/ShareAccessComponent.js +36 -23
- package/lib/components/sharing/SharingEditor.d.ts +17 -0
- package/lib/components/sharing/SharingEditor.js +0 -24
- package/lib/components/sparklines/Sparklines.js +3 -4
- package/lib/components/sparklines/index.js +0 -4
- package/lib/components/storage/ContentsBrowser.d.ts +25 -1
- package/lib/components/storage/ContentsBrowser.js +145 -52
- package/lib/components/storage/ContentsBrowserMock.d.ts +3 -0
- package/lib/components/storage/ContentsBrowserMock.js +75 -0
- package/lib/components/storage/ContentsItems.d.ts +2 -1
- package/lib/components/storage/ContentsItems.js +0 -4
- package/lib/components/storage/ContentsTable.d.ts +50 -0
- package/lib/components/storage/ContentsTable.js +134 -0
- package/lib/components/storage/HomeFolderDrive.d.ts +71 -0
- package/lib/components/storage/HomeFolderDrive.js +248 -0
- package/lib/components/storage/index.d.ts +2 -0
- package/lib/components/storage/index.js +2 -0
- package/lib/components/students/StudentItemStatus.d.ts +1 -1
- package/lib/components/students/StudentResultCircle.d.ts +1 -1
- package/lib/components/subnav/SubNav.d.ts +5 -5
- package/lib/components/subnav/SubNav.js +11 -9
- package/lib/components/tables/DataTable.d.ts +1 -1
- package/lib/components/text-reveal/TextRevealAnimation.d.ts +2 -2
- package/lib/components/text-reveal/useTextRevealAnimationLines.d.ts +1 -1
- package/lib/components/time/LiveRelativeTime.d.ts +1 -0
- package/lib/components/time/LiveRelativeTime.js +0 -8
- package/lib/components/time/index.js +0 -4
- package/lib/components/timeline/Timeline.d.ts +1 -1
- package/lib/components/timeline/index.js +0 -4
- package/lib/components/users/PeerIndicator.d.ts +1 -0
- package/lib/components/users/PeerIndicator.js +0 -4
- package/lib/config/Configuration.d.ts +15 -17
- package/lib/config/Configuration.js +7 -0
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +2 -0
- package/lib/hooks/layouts/LayoutBackdrop.d.ts +1 -1
- package/lib/hooks/layouts/LayoutScreencapture.d.ts +1 -1
- package/lib/hooks/useBackdrop.d.ts +1 -1
- package/lib/hooks/useBackdropJupyterLab.d.ts +3 -2
- package/lib/hooks/useBackdropJupyterLab.js +0 -4
- package/lib/hooks/useCache.d.ts +310 -105
- package/lib/hooks/useCache.js +142 -355
- package/lib/hooks/useContainsFocus.d.ts +1 -1
- package/lib/hooks/useContents.d.ts +313 -0
- package/lib/hooks/useContents.js +964 -0
- package/lib/hooks/useFocusTrap.d.ts +4 -4
- package/lib/hooks/useFocusTrap.js +1 -1
- package/lib/hooks/useMcp.d.ts +229 -0
- package/lib/hooks/useMcp.js +656 -0
- package/lib/hooks/useMobile.js +0 -5
- package/lib/hooks/useProjectStore.d.ts +3 -3
- package/lib/hooks/useProjectStore.js +4 -4
- package/lib/hooks/useRef.d.ts +1 -1
- package/lib/hooks/useRef.js +3 -0
- package/lib/hooks/useScreencapture.d.ts +2 -1
- package/lib/hooks/useScreencapture.js +0 -4
- package/lib/hooks/useToast.js +0 -4
- package/lib/hooks/useVisibilityObserver.d.ts +1 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +3 -1
- package/lib/mocks/components/FlashMock.d.ts +1 -1
- package/lib/mocks/views/ActionMenuMock.d.ts +1 -1
- package/lib/mocks/views/ChartMock.d.ts +1 -1
- package/lib/mocks/views/DashboardMock.d.ts +1 -1
- package/lib/mocks/views/FormMock.d.ts +1 -1
- package/lib/mocks/views/TableMock.d.ts +1 -1
- package/lib/mocks/views/WipMock.d.ts +1 -1
- package/lib/models/Credits.d.ts +16 -0
- package/lib/models/Credits.js +22 -0
- package/lib/models/McpAuditEvent.d.ts +52 -0
- package/lib/models/McpAuditEvent.js +5 -0
- package/lib/models/McpBinding.d.ts +51 -0
- package/lib/models/McpBinding.js +5 -0
- package/lib/models/McpPolicy.d.ts +77 -0
- package/lib/models/McpPolicy.js +5 -0
- package/lib/models/McpTask.d.ts +94 -0
- package/lib/models/McpTask.js +7 -0
- package/lib/models/StartedBy.d.ts +57 -0
- package/lib/models/StartedBy.js +81 -0
- package/lib/models/UserDTO.js +4 -2
- package/lib/models/contents/__fixtures__/solr-codec.json +57 -0
- package/lib/models/contents/__fixtures__/v1-contracts.json +66 -0
- package/lib/models/contents/fieldCase.d.ts +8 -0
- package/lib/models/contents/fieldCase.js +37 -0
- package/lib/models/contents/index.d.ts +2 -0
- package/lib/models/contents/index.js +6 -0
- package/lib/models/contents/permissions.d.ts +16 -0
- package/lib/models/contents/permissions.js +34 -0
- package/lib/models/index.d.ts +5 -1
- package/lib/models/index.js +5 -1
- package/lib/state/substates/CoreState.d.ts +15 -7
- package/lib/state/substates/CoreState.js +18 -14
- package/lib/state/substates/IAMState.d.ts +1 -1
- package/lib/state/substates/IAMState.js +5 -5
- package/lib/state/substates/ProfileState.js +0 -4
- package/lib/state/substates/index.d.ts +0 -1
- package/lib/state/substates/index.js +0 -1
- package/lib/utils/Jwt.d.ts +26 -0
- package/lib/utils/Jwt.js +37 -0
- package/lib/utils/WithSuspense.d.ts +1 -1
- package/lib/views/api-keys/APIKeyEdit.d.ts +1 -1
- package/lib/views/api-keys/APIKeyNew.d.ts +1 -1
- package/lib/views/api-keys/APIKeys.d.ts +1 -1
- package/lib/views/api-keys/APIKeys.js +30 -4
- package/lib/views/api-keys/APIKeysStandalone.d.ts +5 -1
- package/lib/views/api-keys/APIKeysStandalone.js +2 -2
- package/lib/views/datasources/DatasourceDetail.d.ts +12 -5
- package/lib/views/datasources/DatasourceDetail.js +80 -110
- package/lib/views/datasources/DatasourceNew.d.ts +14 -8
- package/lib/views/datasources/DatasourceNew.js +129 -118
- package/lib/views/datasources/Datasources.d.ts +39 -7
- package/lib/views/datasources/Datasources.js +95 -40
- package/lib/views/datasources/DatasourcesMock.d.ts +3 -0
- package/lib/views/datasources/DatasourcesMock.js +57 -0
- package/lib/views/iam/SignInSimple.d.ts +12 -0
- package/lib/views/iam/SignInSimple.js +4 -4
- package/lib/views/index.d.ts +1 -0
- package/lib/views/index.js +1 -0
- package/lib/views/mcp/AlertDestinations.d.ts +34 -0
- package/lib/views/mcp/AlertDestinations.js +75 -0
- package/lib/views/mcp/AlertRules.d.ts +32 -0
- package/lib/views/mcp/AlertRules.js +245 -0
- package/lib/views/mcp/AuditLog.d.ts +42 -0
- package/lib/views/mcp/AuditLog.js +144 -0
- package/lib/views/mcp/ConnectedAgents.d.ts +34 -0
- package/lib/views/mcp/ConnectedAgents.js +100 -0
- package/lib/views/mcp/EnterpriseConsole.d.ts +74 -0
- package/lib/views/mcp/EnterpriseConsole.js +173 -0
- package/lib/views/mcp/McpDashboard.d.ts +50 -0
- package/lib/views/mcp/McpDashboard.js +347 -0
- package/lib/views/mcp/McpHome.d.ts +92 -0
- package/lib/views/mcp/McpHome.js +153 -0
- package/lib/views/mcp/McpObservability.d.ts +50 -0
- package/lib/views/mcp/McpObservability.js +150 -0
- package/lib/views/mcp/OrganizationPolicy.d.ts +45 -0
- package/lib/views/mcp/OrganizationPolicy.js +107 -0
- package/lib/views/mcp/OrganizationUsage.d.ts +69 -0
- package/lib/views/mcp/OrganizationUsage.js +113 -0
- package/lib/views/mcp/PersonalPolicy.d.ts +54 -0
- package/lib/views/mcp/PersonalPolicy.js +125 -0
- package/lib/views/mcp/Policies.d.ts +53 -0
- package/lib/views/mcp/Policies.js +210 -0
- package/lib/views/mcp/PolicyForm.d.ts +67 -0
- package/lib/views/mcp/PolicyForm.js +91 -0
- package/lib/views/mcp/PolicyHistory.d.ts +45 -0
- package/lib/views/mcp/PolicyHistory.js +62 -0
- package/lib/views/mcp/ServiceAgents.d.ts +46 -0
- package/lib/views/mcp/ServiceAgents.js +194 -0
- package/lib/views/mcp/TeamPolicies.d.ts +33 -0
- package/lib/views/mcp/TeamPolicies.js +98 -0
- package/lib/views/mcp/format.d.ts +25 -0
- package/lib/views/mcp/format.js +77 -0
- package/lib/views/mcp/index.d.ts +29 -0
- package/lib/views/mcp/index.js +37 -0
- package/lib/views/mcp/types.d.ts +66 -0
- package/lib/views/mcp/types.js +5 -0
- package/lib/views/secrets/SecretEdit.d.ts +5 -1
- package/lib/views/secrets/SecretEdit.js +2 -2
- package/lib/views/secrets/SecretNew.d.ts +11 -1
- package/lib/views/secrets/SecretNew.js +2 -2
- package/lib/views/secrets/Secrets.d.ts +12 -1
- package/lib/views/secrets/Secrets.js +2 -2
- package/package.json +28 -24
- package/scripts/generate-contents-types.py +169 -0
- package/scripts/generate-mcp-types.py +233 -0
- package/lib/api/iam/datasources.d.ts +0 -86
- package/lib/api/iam/datasources.js +0 -185
- package/lib/components/toolbars/index.d.ts +0 -1
- package/lib/models/Datasource.d.ts +0 -181
- package/lib/models/Datasource.js +0 -154
- package/lib/state/substates/DatasourceState.d.ts +0 -11
- package/lib/state/substates/DatasourceState.js +0 -16
- /package/lib/{components/toolbars/index.js → api/contents/generated.js} +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
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, Heading, Label, Link, Flash, RelativeTime, Select, Spinner, Text, TextInput, } from '@primer/react';
|
|
4
|
+
import { Blankslate, Dialog } from '@primer/react/experimental';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { CheckCircleIcon, ChevronLeftIcon, ChevronRightIcon, DownloadIcon, HistoryIcon, ShieldLockIcon, XCircleIcon, } from '@primer/octicons-react';
|
|
7
|
+
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
|
+
import { useAuditEvents, useAuditExport, useMcpForwarding, } from '../../hooks/useMcp.js';
|
|
9
|
+
import { useNavigate, useToast } from '../../hooks/index.js';
|
|
10
|
+
import { durationLabel } from './format.js';
|
|
11
|
+
/** How many rows a page holds. */
|
|
12
|
+
const PAGE_SIZE = 50;
|
|
13
|
+
const decisionLook = (event) => {
|
|
14
|
+
if (event.decision === 'refused') {
|
|
15
|
+
return { fg: 'danger.fg', bg: 'danger.subtle', Icon: ShieldLockIcon };
|
|
16
|
+
}
|
|
17
|
+
if (event.outcome && event.outcome !== 'ok') {
|
|
18
|
+
return { fg: 'attention.fg', bg: 'attention.subtle', Icon: XCircleIcon };
|
|
19
|
+
}
|
|
20
|
+
return { fg: 'success.fg', bg: 'success.subtle', Icon: CheckCircleIcon };
|
|
21
|
+
};
|
|
22
|
+
/** Save a document the browser never navigated to. */
|
|
23
|
+
const download = (content, filename, mime) => {
|
|
24
|
+
const url = URL.createObjectURL(new Blob([content], { type: mime }));
|
|
25
|
+
const anchor = document.createElement('a');
|
|
26
|
+
anchor.href = url;
|
|
27
|
+
anchor.download = filename;
|
|
28
|
+
anchor.click();
|
|
29
|
+
URL.revokeObjectURL(url);
|
|
30
|
+
};
|
|
31
|
+
export const AuditLog = ({ errorState, routes, filters, onFiltersChange, retentionDays, showTitle = true, subject = 'your agents', }) => {
|
|
32
|
+
const navigate = useNavigate();
|
|
33
|
+
const { enqueueToast } = useToast();
|
|
34
|
+
const page = useAuditEvents({ limit: PAGE_SIZE, ...filters });
|
|
35
|
+
const exporting = useAuditExport();
|
|
36
|
+
// Only an organization forwards; a personal audit has nowhere to go.
|
|
37
|
+
const forwarding = useMcpForwarding(filters.org ?? '', {
|
|
38
|
+
enabled: Boolean(filters.org),
|
|
39
|
+
});
|
|
40
|
+
const [open, setOpen] = useState(null);
|
|
41
|
+
// Primer 37 types Dialog's focus refs with React 18's non-nullable
|
|
42
|
+
// `RefObject<HTMLElement>`. React 19's `useRef(null)` is nullable, and this
|
|
43
|
+
// ref is only handed to Dialog, so narrow it once here.
|
|
44
|
+
const returnFocusRef = useRef(null);
|
|
45
|
+
/** The cursors already walked, so "Newer" is a step back rather than a guess. */
|
|
46
|
+
const [walked, setWalked] = useState([]);
|
|
47
|
+
const rows = page.data?.items ?? [];
|
|
48
|
+
const nextCursor = page.data?.nextCursor ?? null;
|
|
49
|
+
const set = (patch) => {
|
|
50
|
+
// Any change to what is selected starts the walk again: a cursor is a
|
|
51
|
+
// position in one query's answer, and means nothing in another's.
|
|
52
|
+
setWalked([]);
|
|
53
|
+
onFiltersChange({ ...filters, ...patch, cursor: undefined });
|
|
54
|
+
};
|
|
55
|
+
const older = () => {
|
|
56
|
+
if (!nextCursor) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
setWalked(current => [...current, filters.cursor ?? '']);
|
|
60
|
+
onFiltersChange({ ...filters, cursor: nextCursor });
|
|
61
|
+
};
|
|
62
|
+
const newer = () => {
|
|
63
|
+
const previous = walked[walked.length - 1];
|
|
64
|
+
setWalked(current => current.slice(0, -1));
|
|
65
|
+
onFiltersChange({ ...filters, cursor: previous || undefined });
|
|
66
|
+
};
|
|
67
|
+
const exportAs = (format) => {
|
|
68
|
+
const { cursor: _cursor, limit: _limit, ...selection } = filters;
|
|
69
|
+
exporting.mutate({ filters: selection, format }, {
|
|
70
|
+
onSuccess: content => {
|
|
71
|
+
download(content, `mcp-audit-${new Date().toISOString().slice(0, 10)}.${format}`, format === 'csv' ? 'text/csv' : 'application/x-ndjson');
|
|
72
|
+
},
|
|
73
|
+
onError: reason => enqueueToast(`Could not export: ${reason.message}`, {
|
|
74
|
+
variant: 'error',
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const retention = useMemo(() => retentionDays
|
|
79
|
+
? `Rows are kept for ${retentionDays} days on this plan, then deleted.`
|
|
80
|
+
: 'Rows are kept for the retention period of this plan, then deleted.', [retentionDays]);
|
|
81
|
+
if (page.isError) {
|
|
82
|
+
return (_jsx(McpErrorBlankslate, { state: errorState(page.error, 'Audit'), onRetry: () => page.refetch() }));
|
|
83
|
+
}
|
|
84
|
+
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: "Audit" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: ["Every call ", subject, " made through MCP, what was decided, and why."] })] })), forwarding.data?.configured && forwarding.data.state && (_jsx(Flash, { variant: forwarding.data.state.healthy ? 'default' : 'danger', children: _jsx(Text, { sx: { fontSize: 1 }, children: forwarding.data.state.healthy ? (_jsxs(_Fragment, { children: ["Forwarded to your own system of record \u2014", ' ', forwarding.data.state.delivered, " batches", forwarding.data.state.lastDeliveredAt && (_jsxs(_Fragment, { children: [", last", ' ', _jsx(RelativeTime, { datetime: forwarding.data.state.lastDeliveredAt })] })), "."] })) : (_jsxs(_Fragment, { children: [_jsx("strong", { children: "Forwarding is failing." }), " These rows are kept here and are not reaching your own system of record:", ' ', forwarding.data.state.lastError, forwarding.data.state.lastErrorAt && (_jsxs(_Fragment, { children: [' ', "(", _jsx(RelativeTime, { datetime: forwarding.data.state.lastErrorAt }), ")"] })), ". ", forwarding.data.state.failed, " batches have failed."] })) }) })), _jsxs(Box, { sx: {
|
|
85
|
+
display: 'grid',
|
|
86
|
+
gap: 2,
|
|
87
|
+
gridTemplateColumns: ['1fr', 'repeat(2, 1fr)', 'repeat(5, 1fr)'],
|
|
88
|
+
alignItems: 'end',
|
|
89
|
+
}, children: [_jsx(TextInput, { size: "small", "aria-label": "Agent", placeholder: "Agent", value: filters.agent ?? '', onChange: event => set({ agent: event.currentTarget.value || undefined }) }), _jsx(TextInput, { size: "small", "aria-label": "Tool", placeholder: "Tool", value: filters.tool ?? '', onChange: event => set({ tool: event.currentTarget.value || undefined }) }), _jsxs(Select, { size: "small", "aria-label": "Decision", value: filters.decision ?? '', onChange: event => set({
|
|
90
|
+
decision: (event.currentTarget.value ||
|
|
91
|
+
undefined),
|
|
92
|
+
}), children: [_jsx(Select.Option, { value: "", children: "Every decision" }), _jsx(Select.Option, { value: "allowed", children: "Allowed" }), _jsx(Select.Option, { value: "refused", children: "Refused" })] }), _jsxs(Select, { size: "small", "aria-label": "Outcome", value: filters.outcome ?? '', onChange: event => set({
|
|
93
|
+
outcome: (event.currentTarget.value ||
|
|
94
|
+
undefined),
|
|
95
|
+
}), children: [_jsx(Select.Option, { value: "", children: "Every outcome" }), _jsx(Select.Option, { value: "ok", children: "Succeeded" }), _jsx(Select.Option, { value: "error", children: "Failed" }), _jsx(Select.Option, { value: "is_error", children: "Tool error" })] }), _jsx(Box, { sx: { display: 'flex', gap: 2, justifyContent: 'flex-end' }, children: _jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", leadingVisual: DownloadIcon, disabled: exporting.isPending, children: "Export" }), _jsx(ActionMenu.Overlay, { align: "end", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Item, { onSelect: () => exportAs('jsonl'), children: ["JSONL", _jsx(ActionList.Description, { variant: "block", children: "One row per line, as a SIEM reads it." })] }), _jsxs(ActionList.Item, { onSelect: () => exportAs('csv'), children: ["CSV", _jsx(ActionList.Description, { variant: "block", children: "The same rows for a spreadsheet." })] })] }) })] }) })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Audit rows are written once and never changed or deleted by anyone, here or elsewhere. Arguments are redacted before they are written.", ' ', retention] }), page.isPending && !page.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 6 }, children: _jsx(Spinner, {}) })) : rows.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(HistoryIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Nothing to audit yet" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Every call an agent makes is written down here with its decision and its outcome. Connect a client and ask it to list your notebooks." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.access), children: "Connect an agent" })] })) : (
|
|
96
|
+
/* The rail of the activity view: each mark on a tinted disc, the discs
|
|
97
|
+
linked by a line, so the rows carry no border of their own. */
|
|
98
|
+
_jsx(Box, { sx: { display: 'grid' }, children: rows.map((event, index) => {
|
|
99
|
+
const look = decisionLook(event);
|
|
100
|
+
const isLast = index === rows.length - 1;
|
|
101
|
+
return (_jsxs(Box, { sx: { display: 'flex', gap: 3 }, children: [_jsxs(Box, { sx: {
|
|
102
|
+
display: 'flex',
|
|
103
|
+
flexDirection: 'column',
|
|
104
|
+
alignItems: 'center',
|
|
105
|
+
}, children: [_jsx(Box, { sx: {
|
|
106
|
+
width: 26,
|
|
107
|
+
height: 26,
|
|
108
|
+
borderRadius: '50%',
|
|
109
|
+
display: 'inline-flex',
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
justifyContent: 'center',
|
|
112
|
+
color: look.fg,
|
|
113
|
+
bg: look.bg,
|
|
114
|
+
flexShrink: 0,
|
|
115
|
+
}, children: _jsx(look.Icon, { size: 13 }) }), !isLast && (_jsx(Box, { sx: { width: '2px', flex: 1, bg: 'border.muted', my: 1 } }))] }), _jsxs(Box, { sx: {
|
|
116
|
+
flexGrow: 1,
|
|
117
|
+
minWidth: 0,
|
|
118
|
+
pb: isLast ? 0 : 3,
|
|
119
|
+
pt: '3px',
|
|
120
|
+
}, children: [_jsx(Link, { as: "button", sx: {
|
|
121
|
+
fontSize: 1,
|
|
122
|
+
display: 'block',
|
|
123
|
+
textAlign: 'left',
|
|
124
|
+
cursor: 'pointer',
|
|
125
|
+
}, onClick: () => setOpen(event), children: event.tool || event.method }), _jsxs(Box, { sx: {
|
|
126
|
+
display: 'flex',
|
|
127
|
+
gap: 2,
|
|
128
|
+
alignItems: 'center',
|
|
129
|
+
mt: 1,
|
|
130
|
+
flexWrap: 'wrap',
|
|
131
|
+
}, children: [_jsx(Label, { size: "small", variant: event.decision === 'refused' ? 'danger' : 'success', children: event.decision }), event.clientId && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: event.clientId })), event.itemUid && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["on ", event.itemUid] })), event.durationMs !== null &&
|
|
132
|
+
event.durationMs !== undefined && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: durationLabel(event.durationMs) })), event.refusalReason && (_jsx(Text, { sx: { fontSize: 0, color: 'danger.fg' }, children: event.refusalReason }))] })] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: _jsx(RelativeTime, { datetime: event.at }) })] }, event.uid));
|
|
133
|
+
}) })), (walked.length > 0 || nextCursor) && (_jsxs(Box, { sx: { display: 'flex', gap: 2, justifyContent: 'center' }, children: [_jsx(Button, { size: "small", leadingVisual: ChevronLeftIcon, disabled: walked.length === 0, onClick: newer, children: "Newer" }), _jsx(Button, { size: "small", trailingVisual: ChevronRightIcon, disabled: !nextCursor, onClick: older, children: "Older" })] })), open && (_jsx(Dialog, { title: open.tool || open.method, subtitle: `${open.decision} · ${open.outcome ?? 'no outcome recorded'}`, onClose: () => setOpen(null), returnFocusRef: returnFocusRef, width: "large", children: _jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "When" }), _jsxs(Text, { sx: { fontSize: 1 }, children: [_jsx(RelativeTime, { datetime: open.at }), " \u2014 ", open.at] })] }), _jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Who" }), _jsxs(Text, { sx: { fontSize: 1 }, children: [open.clientId || 'unknown client', open.act &&
|
|
134
|
+
open.act.length > 0 &&
|
|
135
|
+
` acting for ${open.act.join(' → ')}`] })] }), open.refusalReason && (_jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Why it was refused" }), _jsx(Text, { sx: { fontSize: 1, color: 'danger.fg' }, children: open.refusalReason })] })), _jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Arguments, redacted before they were written" }), _jsx(Box, { as: "pre", sx: {
|
|
136
|
+
m: 0,
|
|
137
|
+
p: 2,
|
|
138
|
+
fontSize: 0,
|
|
139
|
+
bg: 'canvas.inset',
|
|
140
|
+
borderRadius: 2,
|
|
141
|
+
overflowX: 'auto',
|
|
142
|
+
}, children: JSON.stringify(open.redactedArguments ?? {}, null, 2) }), open.argumentsHash && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle', fontFamily: 'mono' }, children: open.argumentsHash }))] }), _jsxs(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: [open.taskId && (_jsx(Button, { size: "small", onClick: () => navigate(`${routes.runs}/${encodeURIComponent(open.taskId)}`), children: "The run" })), open.traceId && (_jsx(Button, { size: "small", onClick: () => navigate(`${routes.observability}?trace=${encodeURIComponent(open.traceId)}`), children: "The trace" }))] })] }) }))] }));
|
|
143
|
+
};
|
|
144
|
+
export default AuditLog;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agents a person has connected, and the one action that matters:
|
|
3
|
+
* disconnecting one.
|
|
4
|
+
*
|
|
5
|
+
* A grant is not a setting — it is authority a person handed to a piece of
|
|
6
|
+
* software — so the table says which client holds it, what it was allowed
|
|
7
|
+
* to do, when it last used it, and how its identity was established. A
|
|
8
|
+
* client registered by Client ID Metadata Document *is* its URL, and the
|
|
9
|
+
* URL is what is shown as its id: the host is the part nobody can invent.
|
|
10
|
+
*
|
|
11
|
+
* Disconnecting revokes the grant. The refresh token stops working at once
|
|
12
|
+
* and the next call is refused; an access token the agent still holds dies
|
|
13
|
+
* with its own short expiry, which is why they are short. The dialog says
|
|
14
|
+
* so rather than implying an instantaneous kill.
|
|
15
|
+
*
|
|
16
|
+
* @module views/mcp/ConnectedAgents
|
|
17
|
+
*/
|
|
18
|
+
import type { JSX } from 'react';
|
|
19
|
+
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
20
|
+
export interface ConnectedAgentsProps {
|
|
21
|
+
/** The application's words for a failed request. */
|
|
22
|
+
errorState: McpErrorStateFn;
|
|
23
|
+
/** Where this application puts the surfaces this view links to. */
|
|
24
|
+
routes: McpRoutes;
|
|
25
|
+
/** Drawn without its heading, when the page around it carries one. */
|
|
26
|
+
showTitle?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Read-only: an owner looking at the delegated grants of an
|
|
29
|
+
* organization's members may see them, and asks the member to revoke.
|
|
30
|
+
*/
|
|
31
|
+
readOnly?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare const ConnectedAgents: ({ errorState, routes, showTitle, readOnly, }: ConnectedAgentsProps) => JSX.Element;
|
|
34
|
+
export default ConnectedAgents;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { ActionList, ActionMenu, Button, Heading, Label, RelativeTime, Spinner, Text, } from '@primer/react';
|
|
4
|
+
import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { KebabHorizontalIcon, PlugIcon } from '@primer/octicons-react';
|
|
7
|
+
import { ClientBadge, McpErrorBlankslate, ScopeList, } from '../../components/mcp/index.js';
|
|
8
|
+
import { useConnectedAgents, useDisconnectAgent } from '../../hooks/useMcp.js';
|
|
9
|
+
import { useNavigate, useToast } from '../../hooks/index.js';
|
|
10
|
+
export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly = false, }) => {
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const { enqueueToast } = useToast();
|
|
13
|
+
const agents = useConnectedAgents();
|
|
14
|
+
const disconnect = useDisconnectAgent();
|
|
15
|
+
const [disconnecting, setDisconnecting] = useState(null);
|
|
16
|
+
// Primer 37 types Dialog's focus refs with React 18's non-nullable
|
|
17
|
+
// `RefObject<HTMLElement>`. React 19's `useRef(null)` is nullable, and this
|
|
18
|
+
// ref is only handed to Dialog, so narrow it once here.
|
|
19
|
+
const returnFocusRef = useRef(null);
|
|
20
|
+
const rows = useMemo(() => (agents.data ?? []).map(agent => ({ ...agent, id: agent.uid })), [agents.data]);
|
|
21
|
+
const confirmDisconnect = () => {
|
|
22
|
+
const agent = disconnecting;
|
|
23
|
+
if (!agent) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
disconnect.mutate(agent.uid, {
|
|
27
|
+
onSuccess: () => {
|
|
28
|
+
enqueueToast(`${agent.clientName || agent.clientId} is disconnected.`, {
|
|
29
|
+
variant: 'success',
|
|
30
|
+
});
|
|
31
|
+
setDisconnecting(null);
|
|
32
|
+
},
|
|
33
|
+
onError: reason => {
|
|
34
|
+
enqueueToast(`Could not disconnect: ${reason.message}`, {
|
|
35
|
+
variant: 'error',
|
|
36
|
+
});
|
|
37
|
+
setDisconnecting(null);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const columns = [
|
|
42
|
+
{
|
|
43
|
+
header: 'Client',
|
|
44
|
+
field: 'clientId',
|
|
45
|
+
rowHeader: true,
|
|
46
|
+
renderCell: row => (_jsx(ClientBadge, { clientId: row.clientId, clientName: row.clientName, registration: row.registration, clientHostname: row.clientHostname })),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
header: 'Kind',
|
|
50
|
+
id: 'kind',
|
|
51
|
+
width: '110px',
|
|
52
|
+
// Every grant IAM answers here is a delegation of the signed-in
|
|
53
|
+
// person; an organization's service agents are its own principals and
|
|
54
|
+
// are listed on the organization's page from milestone 3.
|
|
55
|
+
renderCell: () => (_jsx(Label, { size: "small", variant: "secondary", children: "Delegated" })),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
header: 'Allowed to',
|
|
59
|
+
id: 'scopes',
|
|
60
|
+
width: 'growCollapse',
|
|
61
|
+
renderCell: row => _jsx(ScopeList, { scopes: row.scopes }),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
header: 'Connected',
|
|
65
|
+
id: 'created',
|
|
66
|
+
width: '130px',
|
|
67
|
+
renderCell: row => row.createdAt ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: _jsx(RelativeTime, { datetime: row.createdAt }) })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "\u2014" })),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
header: 'Last used',
|
|
71
|
+
id: 'last-used',
|
|
72
|
+
width: '130px',
|
|
73
|
+
renderCell: row => row.lastUsedAt ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: _jsx(RelativeTime, { datetime: row.lastUsedAt }) })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Never" })),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
header: '',
|
|
77
|
+
id: 'actions',
|
|
78
|
+
width: '48px',
|
|
79
|
+
align: 'end',
|
|
80
|
+
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.runs}?agent=${encodeURIComponent(row.clientId)}`), children: "Runs" }), _jsx(ActionList.Item, { onSelect: () => navigate(`${routes.audit}?agent=${encodeURIComponent(row.clientId)}`), children: "Audit" }), !readOnly && (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsx(ActionList.Item, { variant: "danger", onSelect: () => setDisconnecting(row), children: "Disconnect" })] }))] }) })] })),
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
if (agents.isError) {
|
|
84
|
+
return (_jsx(McpErrorBlankslate, { state: errorState(agents.error, 'Connected agents'), onRetry: () => agents.refetch() }));
|
|
85
|
+
}
|
|
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: [
|
|
87
|
+
{
|
|
88
|
+
buttonType: 'default',
|
|
89
|
+
content: 'Keep it',
|
|
90
|
+
onClick: () => setDisconnecting(null),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
buttonType: 'danger',
|
|
94
|
+
content: 'Disconnect',
|
|
95
|
+
onClick: confirmDisconnect,
|
|
96
|
+
disabled: disconnect.isPending,
|
|
97
|
+
},
|
|
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."] }) }))] }));
|
|
99
|
+
};
|
|
100
|
+
export default ConnectedAgents;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One console for an administrator of an organization, rather than four
|
|
3
|
+
* unrelated settings tabs.
|
|
4
|
+
*
|
|
5
|
+
* Every page here is the person's own view of the same Core pieces with the
|
|
6
|
+
* organization as the scope: the audit page *is* the audit view, the
|
|
7
|
+
* observability page *is* the observability view. There is one component to
|
|
8
|
+
* test per surface, and an administrator who has learned the personal page
|
|
9
|
+
* has learned this one.
|
|
10
|
+
*
|
|
11
|
+
* Milestone 1 carried Overview, Agents, Audit and Observability, read-only
|
|
12
|
+
* over what exists. **Service Agents** joins them in milestone 3, and is the
|
|
13
|
+
* one page here that *writes*: an organization's own principals, created,
|
|
14
|
+
* rotated and revoked from it. Teams, Policies, Quotas, Alerts, Approvals
|
|
15
|
+
* and Runs join as the layers behind them ship; the sub-navigation says so
|
|
16
|
+
* rather than hiding what is coming.
|
|
17
|
+
*
|
|
18
|
+
* Roles: an `organization_owner` sees everything; an
|
|
19
|
+
* `organization_security_auditor` sees the Overview, the Service Agents,
|
|
20
|
+
* the Audit and the Observability, which is the directory an auditor asks
|
|
21
|
+
* for — the service agents because an audit row naming `agent_uid` needs
|
|
22
|
+
* somewhere to say what that agent is, and the list carries no key; an
|
|
23
|
+
* `organization_usage_reviewer` sees the Overview. Anyone else is told
|
|
24
|
+
* plainly that this is not theirs to read.
|
|
25
|
+
*
|
|
26
|
+
* The auditor's view of Service Agents is read-only, and the page is told
|
|
27
|
+
* so rather than working it out: IAM refuses their writes either way, and a
|
|
28
|
+
* button that exists to be refused is worse than no button.
|
|
29
|
+
*
|
|
30
|
+
* @module views/mcp/EnterpriseConsole
|
|
31
|
+
*/
|
|
32
|
+
import type { JSX } from 'react';
|
|
33
|
+
import type { McpAuditFilters } from '../../api/mcp';
|
|
34
|
+
import { type McpObservabilityPane } from './McpObservability';
|
|
35
|
+
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
36
|
+
/** The pages milestone 1 carries. */
|
|
37
|
+
export type EnterpriseConsolePage = 'overview' | 'usage' | 'agents' | 'service-agents' | 'policy' | 'teams' | 'alerts' | 'audit' | 'observability';
|
|
38
|
+
export declare const ENTERPRISE_CONSOLE_PAGES: {
|
|
39
|
+
id: EnterpriseConsolePage;
|
|
40
|
+
label: string;
|
|
41
|
+
}[];
|
|
42
|
+
/** Which pages each organization role may read. */
|
|
43
|
+
export declare const pagesForRoles: (roles: string[]) => EnterpriseConsolePage[];
|
|
44
|
+
export interface EnterpriseConsoleProps {
|
|
45
|
+
/** The application's words for a failed request. */
|
|
46
|
+
errorState: McpErrorStateFn;
|
|
47
|
+
/** Where this application puts the surfaces this view links to. */
|
|
48
|
+
routes: McpRoutes;
|
|
49
|
+
/** The organization this console is of. */
|
|
50
|
+
organization: {
|
|
51
|
+
uid: string;
|
|
52
|
+
handle: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
};
|
|
55
|
+
/** The roles the signed-in person holds in this organization. */
|
|
56
|
+
roles: string[];
|
|
57
|
+
/** The page open; the address owns it. */
|
|
58
|
+
page: EnterpriseConsolePage;
|
|
59
|
+
onPageChange: (page: EnterpriseConsolePage) => void;
|
|
60
|
+
/** The audit page's filters, as the address holds them. */
|
|
61
|
+
auditFilters: McpAuditFilters;
|
|
62
|
+
onAuditFiltersChange: (filters: McpAuditFilters) => void;
|
|
63
|
+
/** The observability page's selection, likewise. */
|
|
64
|
+
observabilityPane: McpObservabilityPane;
|
|
65
|
+
onObservabilityPaneChange: (pane: McpObservabilityPane) => void;
|
|
66
|
+
observabilityTask?: string;
|
|
67
|
+
observabilityTrace?: string;
|
|
68
|
+
onObservabilitySelect: (selection: {
|
|
69
|
+
task?: string;
|
|
70
|
+
trace?: string;
|
|
71
|
+
}) => void;
|
|
72
|
+
}
|
|
73
|
+
export declare const EnterpriseConsole: ({ errorState, routes, organization, roles, page, onPageChange, auditFilters, onAuditFiltersChange, observabilityPane, onObservabilityPaneChange, observabilityTask, observabilityTrace, onObservabilitySelect, }: EnterpriseConsoleProps) => JSX.Element;
|
|
74
|
+
export default EnterpriseConsole;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Heading, Label, SegmentedControl, Spinner, Text, } from '@primer/react';
|
|
3
|
+
import { Blankslate, DataTable, Table } from '@primer/react/experimental';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { AlertIcon, CheckCircleIcon, LockIcon, PlugIcon, } from '@primer/octicons-react';
|
|
6
|
+
import { ClientBadge, McpErrorBlankslate, ScopeList, } from '../../components/mcp/index.js';
|
|
7
|
+
import { useDisconnectAgent, useMcpActivity, useOrgMcpOverview, } from '../../hooks/useMcp.js';
|
|
8
|
+
import { useNavigate, useToast } from '../../hooks/index.js';
|
|
9
|
+
import { AuditLog } from './AuditLog.js';
|
|
10
|
+
import { McpObservability, } from './McpObservability.js';
|
|
11
|
+
import { AlertRules } from './AlertRules.js';
|
|
12
|
+
import { TeamPolicies } from './TeamPolicies.js';
|
|
13
|
+
import { OrganizationUsage } from './OrganizationUsage.js';
|
|
14
|
+
import { OrganizationPolicy } from './OrganizationPolicy.js';
|
|
15
|
+
import { ServiceAgents } from './ServiceAgents.js';
|
|
16
|
+
import { clientStatusOf, plural, timeAgo } from './format.js';
|
|
17
|
+
export const ENTERPRISE_CONSOLE_PAGES = [
|
|
18
|
+
{ id: 'overview', label: 'Overview' },
|
|
19
|
+
{ id: 'usage', label: 'Usage' },
|
|
20
|
+
{ id: 'agents', label: 'Agents' },
|
|
21
|
+
{ id: 'service-agents', label: 'Service Agents' },
|
|
22
|
+
{ id: 'policy', label: 'Policy' },
|
|
23
|
+
{ id: 'teams', label: 'Teams' },
|
|
24
|
+
{ id: 'alerts', label: 'Alerts' },
|
|
25
|
+
{ id: 'audit', label: 'Audit' },
|
|
26
|
+
{ id: 'observability', label: 'Observability' },
|
|
27
|
+
];
|
|
28
|
+
/** Which pages each organization role may read. */
|
|
29
|
+
export const pagesForRoles = (roles) => {
|
|
30
|
+
if (roles.includes('organization_owner')) {
|
|
31
|
+
return [
|
|
32
|
+
'overview',
|
|
33
|
+
'usage',
|
|
34
|
+
'agents',
|
|
35
|
+
'service-agents',
|
|
36
|
+
'policy',
|
|
37
|
+
'teams',
|
|
38
|
+
'alerts',
|
|
39
|
+
'audit',
|
|
40
|
+
'observability',
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
if (roles.includes('organization_security_auditor')) {
|
|
44
|
+
// The service agents too, read-only: an auditor reading a row that names
|
|
45
|
+
// `agent_uid` needs somewhere to find out what that agent is, and the
|
|
46
|
+
// list carries no key.
|
|
47
|
+
// The policy too, read-only: an auditor asked why a call was refused
|
|
48
|
+
// needs to see the rule that refused it, and the page carries no secret.
|
|
49
|
+
return [
|
|
50
|
+
'overview',
|
|
51
|
+
'service-agents',
|
|
52
|
+
'policy',
|
|
53
|
+
// The team layers too: an auditor asked why one team's agents are
|
|
54
|
+
// treated differently needs the layer that treats them so.
|
|
55
|
+
'teams',
|
|
56
|
+
// The alerts too: what an organization watches for is part of the
|
|
57
|
+
// security posture an auditor is there to read.
|
|
58
|
+
'alerts',
|
|
59
|
+
'audit',
|
|
60
|
+
'observability',
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
if (roles.includes('organization_usage_reviewer')) {
|
|
64
|
+
// The page the role is named for. It saw only the Overview, which counts
|
|
65
|
+
// runs and refusals and says nothing about a limit — a usage reviewer
|
|
66
|
+
// with no usage page had nothing to review.
|
|
67
|
+
return ['overview', 'usage'];
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
};
|
|
71
|
+
const Metric = ({ label, value, note, tone = 'neutral', }) => (_jsxs(Box, { sx: {
|
|
72
|
+
p: 3,
|
|
73
|
+
border: '1px solid',
|
|
74
|
+
borderColor: 'border.default',
|
|
75
|
+
borderRadius: 2,
|
|
76
|
+
minWidth: 0,
|
|
77
|
+
}, children: [_jsx(Text, { sx: {
|
|
78
|
+
display: 'block',
|
|
79
|
+
fontSize: 4,
|
|
80
|
+
fontWeight: 'bold',
|
|
81
|
+
color: tone === 'danger' ? 'danger.fg' : 'fg.default',
|
|
82
|
+
}, children: value }), _jsx(Text, { sx: { display: 'block', fontSize: 0, color: 'fg.muted' }, children: label }), note && (_jsx(Text, { sx: { display: 'block', fontSize: 0, color: 'fg.subtle' }, children: note }))] }));
|
|
83
|
+
export const EnterpriseConsole = ({ errorState, routes, organization, roles, page, onPageChange, auditFilters, onAuditFiltersChange, observabilityPane, onObservabilityPaneChange, observabilityTask, observabilityTrace, onObservabilitySelect, }) => {
|
|
84
|
+
const navigate = useNavigate();
|
|
85
|
+
const { enqueueToast } = useToast();
|
|
86
|
+
const allowed = pagesForRoles(roles);
|
|
87
|
+
const current = allowed.includes(page) ? page : allowed[0];
|
|
88
|
+
const overview = useOrgMcpOverview(organization.uid, {}, {
|
|
89
|
+
enabled: Boolean(current) && (current === 'overview' || current === 'agents'),
|
|
90
|
+
});
|
|
91
|
+
const activity = useMcpActivity({ org: organization.uid }, { enabled: current === 'agents' });
|
|
92
|
+
const disconnect = useDisconnectAgent();
|
|
93
|
+
if (allowed.length === 0) {
|
|
94
|
+
return (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(LockIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Unauthorized" }), _jsx(Blankslate.Description, { children: _jsxs(Text, { sx: { textAlign: 'center' }, children: ["The MCP console of an organization is read by its owners, its security auditors and its usage reviewers. Ask an owner of", ' ', organization.name || organization.handle, " for the role you need."] }) })] }));
|
|
95
|
+
}
|
|
96
|
+
const agentRows = (activity.data?.clients ?? []).map(client => ({
|
|
97
|
+
...client,
|
|
98
|
+
id: client.clientId,
|
|
99
|
+
}));
|
|
100
|
+
const agentColumns = [
|
|
101
|
+
{
|
|
102
|
+
header: 'Client',
|
|
103
|
+
field: 'clientId',
|
|
104
|
+
rowHeader: true,
|
|
105
|
+
renderCell: row => (_jsx(ClientBadge, { clientId: row.clientId, clientName: row.clientName })),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
header: 'Member',
|
|
109
|
+
id: 'member',
|
|
110
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.lastCall?.userUid || '—' })),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
header: 'Allowed to',
|
|
114
|
+
id: 'scopes',
|
|
115
|
+
width: 'growCollapse',
|
|
116
|
+
renderCell: row => _jsx(ScopeList, { scopes: row.scopes ?? [] }),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
header: 'Last used',
|
|
120
|
+
id: 'last-used',
|
|
121
|
+
width: '120px',
|
|
122
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: timeAgo(row.lastCall?.at) || 'Never' })),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
header: 'Status',
|
|
126
|
+
id: 'status',
|
|
127
|
+
width: '120px',
|
|
128
|
+
renderCell: row => {
|
|
129
|
+
const status = clientStatusOf(row.lastCall?.at);
|
|
130
|
+
return (_jsx(Label, { size: "small", variant: status === 'active'
|
|
131
|
+
? 'success'
|
|
132
|
+
: status === 'idle'
|
|
133
|
+
? 'attention'
|
|
134
|
+
: 'secondary', children: status }));
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
header: '',
|
|
139
|
+
id: 'actions',
|
|
140
|
+
width: '200px',
|
|
141
|
+
align: 'end',
|
|
142
|
+
renderCell: row => (_jsxs(Box, { sx: { display: 'flex', gap: 2, justifyContent: 'flex-end' }, children: [_jsx(Button, { size: "small", onClick: () => navigate(`${routes.audit}?org=${encodeURIComponent(organization.uid)}&agent=${encodeURIComponent(row.clientId)}`), children: "Audit" }), _jsx(Button, { size: "small", variant: "danger", disabled: !row.grantUid, onClick: () => row.grantUid &&
|
|
143
|
+
disconnect.mutate(row.grantUid, {
|
|
144
|
+
onSuccess: () => enqueueToast(`${row.clientName || row.clientId} is disconnected.`, {
|
|
145
|
+
variant: 'success',
|
|
146
|
+
}),
|
|
147
|
+
onError: () => enqueueToast('That grant belongs to another member and is revoked by them today. Revoking on their behalf arrives with the organization agent directory.', { variant: 'error' }),
|
|
148
|
+
}), children: "Revoke" })] })),
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 4, minWidth: 0 }, children: [_jsxs(Box, { children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: ["MCP \u2014 ", organization.name || organization.handle] }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "What the agents of this organization are doing, what they were allowed to do, and how it ran." })] }), _jsx(SegmentedControl, { "aria-label": "MCP console pages", size: "small", children: ENTERPRISE_CONSOLE_PAGES.filter(entry => allowed.includes(entry.id)).map(entry => (_jsx(SegmentedControl.Button, { selected: current === entry.id, onClick: () => onPageChange(entry.id), children: entry.label }, entry.id))) }), current === 'overview' &&
|
|
152
|
+
(overview.isError ? (_jsx(McpErrorBlankslate, { state: errorState(overview.error, 'The overview'), onRetry: () => overview.refetch() })) : overview.isPending && !overview.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 6 }, children: _jsx(Spinner, {}) })) : (_jsxs(Box, { sx: { display: 'grid', gap: 4 }, children: [_jsxs(Box, { sx: {
|
|
153
|
+
display: 'grid',
|
|
154
|
+
gap: 3,
|
|
155
|
+
gridTemplateColumns: [
|
|
156
|
+
'repeat(2, 1fr)',
|
|
157
|
+
'repeat(3, 1fr)',
|
|
158
|
+
'repeat(5, 1fr)',
|
|
159
|
+
],
|
|
160
|
+
}, children: [_jsx(Metric, { label: "Agents active today", value: overview.data?.agents.activeToday ?? 0, note: `${overview.data?.agents.delegated ?? 0} delegated · ${overview.data?.agents.service ?? 0} service` }), _jsx(Metric, { label: "Runs today", value: overview.data?.runs.today ?? 0 }), _jsx(Metric, { label: "Success rate", value: overview.data?.runs.successRate === null ||
|
|
161
|
+
overview.data?.runs.successRate === undefined
|
|
162
|
+
? '—'
|
|
163
|
+
: `${(overview.data.runs.successRate * 100).toFixed(0)}%`, note: `${overview.data?.runs.failed ?? 0} failed` }), _jsx(Metric, { label: "Credits today", value: overview.data?.spend?.creditsToday ?? '—', note: overview.data?.spend?.quotaMonth
|
|
164
|
+
? `${overview.data.spend.creditsMonth} of ${overview.data.spend.quotaMonth} this month`
|
|
165
|
+
: 'Quotas arrive with the organization policy' }), _jsx(Metric, { label: "Refusals today", value: (overview.data?.refusals ?? []).reduce((sum, row) => sum + row.count, 0), tone: (overview.data?.refusals ?? []).length > 0
|
|
166
|
+
? 'danger'
|
|
167
|
+
: '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
|
+
color: check.ok ? 'success.fg' : 'attention.fg',
|
|
169
|
+
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' &&
|
|
171
|
+
(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
|
+
};
|
|
173
|
+
export default EnterpriseConsole;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the connected MCP clients are doing right now.
|
|
3
|
+
*
|
|
4
|
+
* One data call — `GET /api/mcp/v1/activity` — answers the whole page, so
|
|
5
|
+
* the counts and the four panels can never disagree with each other, and
|
|
6
|
+
* the home page's cards, which read the same query, can never disagree with
|
|
7
|
+
* this. It is refetched every ten seconds as the fallback under the
|
|
8
|
+
* ai-agents websocket, which patches the same cache entry.
|
|
9
|
+
*
|
|
10
|
+
* Top: the counts that answer "is anything happening". Then, in the idiom
|
|
11
|
+
* of the runtimes tables, four panels: the clients with a live grant, the
|
|
12
|
+
* tasks running, the sandboxes bound and the calls as they land. Each empty
|
|
13
|
+
* state names the next step rather than saying "nothing here".
|
|
14
|
+
*
|
|
15
|
+
* Milestone 1 draws the clients, the sandboxes, the calls and the counts;
|
|
16
|
+
* the running tasks and the live pane arrive with durable execution in
|
|
17
|
+
* milestone 2 and say so where they will be.
|
|
18
|
+
*
|
|
19
|
+
* @module views/mcp/McpDashboard
|
|
20
|
+
*/
|
|
21
|
+
import type { JSX } from 'react';
|
|
22
|
+
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
23
|
+
export interface McpDashboardProps {
|
|
24
|
+
/** The application's words for a failed request. */
|
|
25
|
+
errorState: McpErrorStateFn;
|
|
26
|
+
/** Where the surfaces this page links to live. */
|
|
27
|
+
/** Where this application puts the surfaces this view links to. */
|
|
28
|
+
routes: McpRoutes;
|
|
29
|
+
/** An owner's view of one organization, rather than the caller's own. */
|
|
30
|
+
org?: string;
|
|
31
|
+
/** Drawn without its heading, when the page around it carries one. */
|
|
32
|
+
showTitle?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Which part of the page to draw.
|
|
35
|
+
*
|
|
36
|
+
* Required, and there is no "draw everything": the whole thing on one page
|
|
37
|
+
* was a column of tables and blankslates a reader scrolled past to find
|
|
38
|
+
* the one they came for. Each part is a tab now, and a caller that has not
|
|
39
|
+
* decided which one it wants has not decided what it is showing.
|
|
40
|
+
*
|
|
41
|
+
* The counts belong to `'overview'` rather than repeating on each tab:
|
|
42
|
+
* they answer "is anything happening", which is the question somebody has
|
|
43
|
+
* *before* choosing where to look.
|
|
44
|
+
*/
|
|
45
|
+
section: McpDashboardSection;
|
|
46
|
+
}
|
|
47
|
+
/** The parts of the dashboard, each its own tab. */
|
|
48
|
+
export type McpDashboardSection = 'overview' | 'agents' | 'runs' | 'sandboxes' | 'calls';
|
|
49
|
+
export declare const McpDashboard: ({ errorState, routes, org, showTitle, section, }: McpDashboardProps) => JSX.Element;
|
|
50
|
+
export default McpDashboard;
|