@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,150 @@
|
|
|
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, SegmentedControl, Spinner, Text, TextInput, } from '@primer/react';
|
|
4
|
+
import { Blankslate } from '@primer/react/experimental';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { SearchIcon, TelescopeIcon } from '@primer/octicons-react';
|
|
7
|
+
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
|
+
import { useMcpMetrics, useMcpTrace, useRunTrace } from '../../hooks/useMcp.js';
|
|
9
|
+
import { useNavigate } from '../../hooks/index.js';
|
|
10
|
+
import { MCP_METRIC_CATALOG } from '../../api/mcp/index.js';
|
|
11
|
+
import { durationLabel } from './format.js';
|
|
12
|
+
/** The gateway's own spans, so the tree can call out what each stage cost. */
|
|
13
|
+
const STAGE = (name) => {
|
|
14
|
+
if (name.startsWith('mcp.policy')) {
|
|
15
|
+
return 'Policy';
|
|
16
|
+
}
|
|
17
|
+
if (name.startsWith('mcp.request')) {
|
|
18
|
+
return 'Gateway';
|
|
19
|
+
}
|
|
20
|
+
if (name.startsWith('sandbox.')) {
|
|
21
|
+
return 'Sandbox';
|
|
22
|
+
}
|
|
23
|
+
if (name.startsWith('durable.')) {
|
|
24
|
+
return 'Workflow';
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
};
|
|
28
|
+
const SpanRow = ({ node, depth, }) => {
|
|
29
|
+
const stage = STAGE(node.span.span_name);
|
|
30
|
+
const failed = (node.span.status_code ?? '').toUpperCase() === 'ERROR';
|
|
31
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
gap: 2,
|
|
35
|
+
py: 1,
|
|
36
|
+
pl: depth * 3,
|
|
37
|
+
borderBottom: '1px solid',
|
|
38
|
+
borderColor: 'border.muted',
|
|
39
|
+
minWidth: 0,
|
|
40
|
+
}, children: [_jsx(Text, { sx: {
|
|
41
|
+
fontSize: 0,
|
|
42
|
+
fontFamily: 'mono',
|
|
43
|
+
flex: 1,
|
|
44
|
+
minWidth: 0,
|
|
45
|
+
overflow: 'hidden',
|
|
46
|
+
textOverflow: 'ellipsis',
|
|
47
|
+
whiteSpace: 'nowrap',
|
|
48
|
+
color: failed ? 'danger.fg' : 'fg.default',
|
|
49
|
+
}, children: node.span.span_name }), stage && (_jsx(Label, { size: "small", variant: "secondary", children: stage })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: node.span.service_name }), _jsx(Text, { sx: {
|
|
50
|
+
fontSize: 0,
|
|
51
|
+
fontWeight: 'semibold',
|
|
52
|
+
minWidth: 60,
|
|
53
|
+
textAlign: 'right',
|
|
54
|
+
}, children: durationLabel(node.span.duration_ms) })] }), node.children.map(child => (_jsx(SpanRow, { node: child, depth: depth + 1 }, child.span.span_id)))] }));
|
|
55
|
+
};
|
|
56
|
+
/** A number the reader can act on, or a plain statement that it is not measured. */
|
|
57
|
+
const Sli = ({ label, value, note, }) => (_jsxs(Box, { sx: {
|
|
58
|
+
p: 3,
|
|
59
|
+
border: '1px solid',
|
|
60
|
+
borderColor: 'border.default',
|
|
61
|
+
borderRadius: 2,
|
|
62
|
+
minWidth: 0,
|
|
63
|
+
}, children: [_jsx(Text, { sx: { display: 'block', fontSize: 3, fontWeight: 'bold' }, 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 }))] }));
|
|
64
|
+
const percentLabel = (value) => value === null || value === undefined
|
|
65
|
+
? 'Not measured'
|
|
66
|
+
: `${(value * 100).toFixed(1)}%`;
|
|
67
|
+
export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskUid, traceId, onSelect, agent, org, showTitle = true, }) => {
|
|
68
|
+
const navigate = useNavigate();
|
|
69
|
+
const [find, setFind] = useState(taskUid ?? traceId ?? '');
|
|
70
|
+
// One of the two, never both: a task names its own trace, and a trace
|
|
71
|
+
// names itself.
|
|
72
|
+
const byTask = useRunTrace(taskUid, { enabled: Boolean(taskUid) });
|
|
73
|
+
const byTrace = useMcpTrace(traceId, {
|
|
74
|
+
enabled: Boolean(traceId) && !taskUid,
|
|
75
|
+
});
|
|
76
|
+
const run = taskUid ? byTask : byTrace;
|
|
77
|
+
const metrics = useMcpMetrics({ agent, org });
|
|
78
|
+
const telemetryNotice = (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["This is telemetry: spans and metrics are sampled, kept for a short time and meant for understanding how a call ran. It is not the record of what was allowed \u2014", ' ', _jsx(Text, { as: "span", sx: { color: 'accent.fg', cursor: 'pointer' }, onClick: () => navigate(routes.audit), children: "the audit log" }), ' ', "is, and it is kept per plan and never changed."] }));
|
|
79
|
+
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: "Observability" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "How the calls of your agents ran: where the time went, and how the service is behaving." })] })), _jsxs(SegmentedControl, { "aria-label": "Observability panes", size: "small", children: [_jsx(SegmentedControl.Button, { selected: pane === 'runs', onClick: () => onPaneChange('runs'), children: "Runs" }), _jsx(SegmentedControl.Button, { selected: pane === 'metrics', onClick: () => onPaneChange('metrics'), children: "Metrics" })] }), telemetryNotice, pane === 'runs' && (_jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(Box, { sx: {
|
|
80
|
+
display: 'flex',
|
|
81
|
+
gap: 2,
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
flexWrap: 'wrap',
|
|
84
|
+
}, children: [_jsx(TextInput, { size: "small", leadingVisual: SearchIcon, "aria-label": "Task or trace id", placeholder: "Task uid or trace id", value: find, sx: { minWidth: 320 }, onChange: event => setFind(event.currentTarget.value) }), _jsx(Button, { size: "small", onClick: () => {
|
|
85
|
+
const value = find.trim();
|
|
86
|
+
if (!value) {
|
|
87
|
+
onSelect({});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// A trace id is 32 hexadecimal characters; anything else is a
|
|
91
|
+
// task uid, and the gateway tells us which by answering.
|
|
92
|
+
onSelect(/^[0-9a-f]{32}$/i.test(value)
|
|
93
|
+
? { trace: value }
|
|
94
|
+
: { task: value });
|
|
95
|
+
}, children: "Show the run" })] }), !taskUid && !traceId ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Pick a run" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Open a call from the audit log or the dashboard, or paste its task uid or trace id here, and its spans across the gateway, the policy check and the worker appear with the time each took." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.audit), children: "Open the audit log" })] })) : run.isError ? (_jsx(McpErrorBlankslate, { state: errorState(run.error, 'This run'), onRetry: () => run.refetch() })) : run.isPending ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : (run.data?.spans.length ?? 0) === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No span for this call" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Spans reach the collector a few seconds after the call and are kept for a short time. A call older than the retention window has an audit row but no trace." }) })] })) : (_jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsxs(Box, { sx: {
|
|
96
|
+
display: 'flex',
|
|
97
|
+
gap: 3,
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
flexWrap: 'wrap',
|
|
100
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: run.data?.traceId }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [run.data?.spans.length, " spans"] }), _jsx(Button, { size: "small", onClick: () => navigate(`${routes.audit}?trace=${encodeURIComponent(run.data?.traceId ?? '')}`), children: "The audit row" })] }), _jsx(Box, { sx: {
|
|
101
|
+
border: '1px solid',
|
|
102
|
+
borderColor: 'border.default',
|
|
103
|
+
borderRadius: 2,
|
|
104
|
+
overflowX: 'auto',
|
|
105
|
+
}, children: (run.data?.tree ?? []).map(node => (_jsx(SpanRow, { node: node, depth: 0 }, node.span.span_id))) }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "The workflow steps and the sandbox launch of a durable task join this tree with milestone 2, and so does the live stream of calls as they land." })] }))] })), pane === 'metrics' && (_jsx(Box, { sx: { display: 'grid', gap: 3 }, children: metrics.isError ? (_jsx(McpErrorBlankslate, { state: errorState(metrics.error, 'Metrics'), onRetry: () => metrics.refetch() })) : metrics.isPending && !metrics.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
106
|
+
display: 'grid',
|
|
107
|
+
gap: 3,
|
|
108
|
+
gridTemplateColumns: [
|
|
109
|
+
'1fr',
|
|
110
|
+
'repeat(2, 1fr)',
|
|
111
|
+
'repeat(4, 1fr)',
|
|
112
|
+
],
|
|
113
|
+
}, children: [_jsx(Sli, { label: "Availability", value: percentLabel(metrics.data?.slis.availability), note: `${metrics.data?.slis.samples.calls ?? 0} calls` }), _jsx(Sli, { label: "Call latency, p95", value: metrics.data?.slis.p95CallDurationMs === null ||
|
|
114
|
+
metrics.data?.slis.p95CallDurationMs === undefined
|
|
115
|
+
? 'Not measured'
|
|
116
|
+
: durationLabel(metrics.data.slis.p95CallDurationMs) }), _jsx(Sli, { label: "Task success", value: percentLabel(metrics.data?.slis.taskSuccessRate), note: `${metrics.data?.slis.samples.tasks ?? 0} tasks` }), _jsx(Sli, { label: "Sandbox launch, p95", value: Object.keys(metrics.data?.slis.p95SandboxLaunchSeconds ?? {}).length === 0
|
|
117
|
+
? 'Not measured'
|
|
118
|
+
: Object.entries(metrics.data?.slis.p95SandboxLaunchSeconds ?? {})
|
|
119
|
+
.map(([provider, seconds]) => `${provider} ${seconds.toFixed(1)}s`)
|
|
120
|
+
.join(' · '), note: `${metrics.data?.slis.samples.launches ?? 0} launches` })] }), _jsx(Box, { sx: {
|
|
121
|
+
border: '1px solid',
|
|
122
|
+
borderColor: 'border.default',
|
|
123
|
+
borderRadius: 2,
|
|
124
|
+
overflowX: 'auto',
|
|
125
|
+
}, children: MCP_METRIC_CATALOG.map(name => {
|
|
126
|
+
const points = metrics.data?.metrics[name] ?? [];
|
|
127
|
+
const last = points[points.length - 1];
|
|
128
|
+
return (_jsxs(Box, { sx: {
|
|
129
|
+
display: 'flex',
|
|
130
|
+
alignItems: 'center',
|
|
131
|
+
gap: 3,
|
|
132
|
+
py: 1,
|
|
133
|
+
px: 2,
|
|
134
|
+
borderBottom: '1px solid',
|
|
135
|
+
borderColor: 'border.muted',
|
|
136
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, fontFamily: 'mono', flex: 1 }, children: name }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: points.length === 0
|
|
137
|
+
? 'no point'
|
|
138
|
+
: `${points.length} points` }), _jsx(Text, { sx: {
|
|
139
|
+
fontSize: 0,
|
|
140
|
+
fontWeight: 'semibold',
|
|
141
|
+
minWidth: 80,
|
|
142
|
+
textAlign: 'right',
|
|
143
|
+
}, children: last
|
|
144
|
+
? `${last.value}${last.unit ? ` ${last.unit}` : ''}`
|
|
145
|
+
: '—' })] }, name));
|
|
146
|
+
}) }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Metrics carry no user, agent or organization label by design, so a per-agent reading is computed from the request spans, which do.", metrics.data?.spans.length
|
|
147
|
+
? ` ${metrics.data.spans.length} spans read.`
|
|
148
|
+
: ''] })] })) }))] }));
|
|
149
|
+
};
|
|
150
|
+
export default McpObservability;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an organization narrows for its agents, and the form that writes it.
|
|
3
|
+
*
|
|
4
|
+
* The sibling `Policies` view is the *read*: every layer intersected, with
|
|
5
|
+
* the layer that decided each rule. This is the *write*, and only one
|
|
6
|
+
* layer's — an organization owner cannot edit the platform's defaults or
|
|
7
|
+
* somebody's personal rules, and would not want to.
|
|
8
|
+
*
|
|
9
|
+
* Three things this page owes the person filling it in:
|
|
10
|
+
*
|
|
11
|
+
* **Only rules the gateway enforces.** IAM refuses anything else, so a form
|
|
12
|
+
* offering more would collect a setting that is thrown away at the write —
|
|
13
|
+
* and `toolDenyList` would read as a working denylist rather than a typo.
|
|
14
|
+
* The fields come from `MCP_POLICY_RULES` so the form cannot drift.
|
|
15
|
+
*
|
|
16
|
+
* **An empty allowlist is not an allowlist.** Leaving the field blank means
|
|
17
|
+
* "I have not set one", not "nothing is permitted" — the second would stop
|
|
18
|
+
* every agent in the organization, and the first conclusion anybody draws
|
|
19
|
+
* from that is that the platform is down. The captions say so where
|
|
20
|
+
* somebody is about to make that mistake.
|
|
21
|
+
*
|
|
22
|
+
* **A limit of zero is refused rather than stored.** The gateway reads a
|
|
23
|
+
* non-positive cap as *no cap*, so a zero written to stop an organization's
|
|
24
|
+
* agents lifts its limit instead — the one value whose stored meaning is the
|
|
25
|
+
* opposite of its plain reading.
|
|
26
|
+
*
|
|
27
|
+
* @module views/mcp/OrganizationPolicy
|
|
28
|
+
*/
|
|
29
|
+
import type { JSX } from 'react';
|
|
30
|
+
import type { McpErrorStateFn } from './types';
|
|
31
|
+
export interface OrganizationPolicyProps {
|
|
32
|
+
/** The application's words for a failed request. */
|
|
33
|
+
errorState: McpErrorStateFn;
|
|
34
|
+
/** The organization whose layer this is. */
|
|
35
|
+
orgUid: string;
|
|
36
|
+
/**
|
|
37
|
+
* Read-only for anybody who is not an owner. IAM refuses their writes
|
|
38
|
+
* either way; this keeps the page from offering an action that would be
|
|
39
|
+
* refused.
|
|
40
|
+
*/
|
|
41
|
+
readOnly?: boolean;
|
|
42
|
+
showTitle?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare const OrganizationPolicy: ({ errorState, orgUid, readOnly, showTitle, }: OrganizationPolicyProps) => JSX.Element;
|
|
45
|
+
export default OrganizationPolicy;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Button, Flash, Heading, Spinner, Text } from '@primer/react';
|
|
4
|
+
import { Dialog } from '@primer/react/experimental';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
7
|
+
import { useDeleteMcpPolicyLayer, useMcpPolicyLayer, useSetMcpPolicyLayer, } from '../../hooks/useMcp.js';
|
|
8
|
+
import { useToast } from '../../hooks/index.js';
|
|
9
|
+
import { McpPolicyConflict } from '../../api/iam/mcpPolicy.js';
|
|
10
|
+
import { PolicyHistory } from './PolicyHistory.js';
|
|
11
|
+
import { EMPTY_POLICY_DRAFT, PolicyForm, draftOf, policyChanged, rulesFrom, } from './PolicyForm.js';
|
|
12
|
+
export const OrganizationPolicy = ({ errorState, orgUid, readOnly = false, showTitle = true, }) => {
|
|
13
|
+
const { enqueueToast } = useToast();
|
|
14
|
+
const layer = useMcpPolicyLayer('organization', orgUid);
|
|
15
|
+
const save = useSetMcpPolicyLayer('organization', orgUid);
|
|
16
|
+
const remove = useDeleteMcpPolicyLayer('organization', orgUid);
|
|
17
|
+
const [draft, setDraft] = useState(EMPTY_POLICY_DRAFT);
|
|
18
|
+
const [removing, setRemoving] = useState(false);
|
|
19
|
+
const [conflict, setConflict] = useState('');
|
|
20
|
+
const [refusal, setRefusal] = useState('');
|
|
21
|
+
/**
|
|
22
|
+
* The version that was read, sent back with the write.
|
|
23
|
+
*
|
|
24
|
+
* `undefined` when nobody has written this layer: inventing a version
|
|
25
|
+
* would make the first write of every policy a conflict.
|
|
26
|
+
*/
|
|
27
|
+
const version = layer.data?.version;
|
|
28
|
+
const stored = useMemo(() => draftOf(layer.data), [layer.data]);
|
|
29
|
+
// The form follows what was read, including after a conflict has been
|
|
30
|
+
// resolved by re-reading: an editor still showing the losing draft is how
|
|
31
|
+
// somebody reapplies the change that was already refused.
|
|
32
|
+
useEffect(() => setDraft(stored), [stored]);
|
|
33
|
+
const changed = useMemo(() => policyChanged(draft, stored), [draft, stored]);
|
|
34
|
+
const set = (key, value) => setDraft(current => ({ ...current, [key]: value }));
|
|
35
|
+
const apply = () => {
|
|
36
|
+
const rules = rulesFrom(draft);
|
|
37
|
+
if (typeof rules === 'string') {
|
|
38
|
+
setRefusal(rules);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
setRefusal('');
|
|
42
|
+
setConflict('');
|
|
43
|
+
save.mutate({ rules, expectedVersion: version }, {
|
|
44
|
+
onSuccess: () => {
|
|
45
|
+
enqueueToast('Policy saved. It applies to the next call.', {
|
|
46
|
+
variant: 'success',
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
onError: error => {
|
|
50
|
+
if (error instanceof McpPolicyConflict) {
|
|
51
|
+
// Not a toast. A toast disappears, and this needs an action —
|
|
52
|
+
// the draft in front of them is the one that lost.
|
|
53
|
+
setConflict(error.message);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
enqueueToast(`Could not save: ${error.message}`, {
|
|
57
|
+
variant: 'error',
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const confirmRemove = () => {
|
|
63
|
+
remove.mutate(undefined, {
|
|
64
|
+
onSuccess: () => {
|
|
65
|
+
setRemoving(false);
|
|
66
|
+
enqueueToast('Policy removed. This layer narrows nothing.', {
|
|
67
|
+
variant: 'success',
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
onError: error => {
|
|
71
|
+
setRemoving(false);
|
|
72
|
+
enqueueToast(`Could not remove: ${error.message}`, {
|
|
73
|
+
variant: 'error',
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
if (layer.isError) {
|
|
79
|
+
return (_jsx(McpErrorBlankslate, { state: errorState(layer.error, "The organization's MCP policy"), onRetry: () => layer.refetch() }));
|
|
80
|
+
}
|
|
81
|
+
if (layer.isPending && layer.data === undefined) {
|
|
82
|
+
return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) }));
|
|
83
|
+
}
|
|
84
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0, maxWidth: '52rem' }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Organization Policy" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "What this organization narrows for every agent acting in it. Each layer only narrows \u2014 a team, or a person, can be stricter than this and never looser." })] })), conflict && (_jsxs(Flash, { variant: "warning", children: [_jsx(Text, { sx: { fontSize: 1 }, children: conflict }), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Button, { size: "small", onClick: () => {
|
|
85
|
+
setConflict('');
|
|
86
|
+
layer.refetch();
|
|
87
|
+
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, disabled: readOnly }), !readOnly && (_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save policy" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { sx: { marginLeft: 'auto' }, children: _jsx(Button, { variant: "danger", onClick: () => setRemoving(true), children: "Remove policy" }) }))] })), _jsxs(Box, { sx: {
|
|
88
|
+
borderTop: '1px solid',
|
|
89
|
+
borderColor: 'border.muted',
|
|
90
|
+
pt: 3,
|
|
91
|
+
display: 'grid',
|
|
92
|
+
gap: 2,
|
|
93
|
+
}, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "History" }), _jsx(PolicyHistory, { orgUid: orgUid, subjectUid: orgUid })] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "Only these rules can be stored. A rule the gateway does not enforce is refused rather than kept, so this page cannot promise something that never happens." }), removing && (_jsx(Dialog, { title: "Remove this policy?", onClose: () => setRemoving(false), footerButtons: [
|
|
94
|
+
{
|
|
95
|
+
buttonType: 'default',
|
|
96
|
+
content: 'Keep it',
|
|
97
|
+
onClick: () => setRemoving(false),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
buttonType: 'danger',
|
|
101
|
+
content: 'Remove',
|
|
102
|
+
onClick: confirmRemove,
|
|
103
|
+
disabled: remove.isPending,
|
|
104
|
+
},
|
|
105
|
+
], children: _jsxs(Text, { sx: { fontSize: 1 }, children: ["This organization stops narrowing anything. Its agents fall back to the platform defaults and to whatever each team and person has set \u2014 which may be ", _jsx("strong", { children: "wider" }), " than what you have now. Nothing already done is undone."] }) }))] }));
|
|
106
|
+
};
|
|
107
|
+
export default OrganizationPolicy;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the organization used, against what it may — and which agent used it.
|
|
3
|
+
*
|
|
4
|
+
* The console had a role called `organization_usage_reviewer` and no page of
|
|
5
|
+
* usage: the reviewer was sent to the Overview, which counts runs and
|
|
6
|
+
* refusals and says nothing about a limit. A budget with no spend against it
|
|
7
|
+
* is a number somebody set once.
|
|
8
|
+
*
|
|
9
|
+
* The breakdown underneath is the half that decides anything. A quota bar at
|
|
10
|
+
* ninety percent tells an administrator to act; it does not say what to turn
|
|
11
|
+
* off. The rows name the agents, biggest spender first, because that is the
|
|
12
|
+
* order the question is asked in.
|
|
13
|
+
*
|
|
14
|
+
* **Unreadable is not zero.** A figure the gateway could not read is drawn
|
|
15
|
+
* as "not available" with the reason, never as a blank or a 0 — they look
|
|
16
|
+
* the same in a dashboard and mean opposite things. For the same reason the
|
|
17
|
+
* breakdown is hidden entirely when the total could not be read: the parts
|
|
18
|
+
* under an unreadable whole make every agent look cheap, and a reader
|
|
19
|
+
* concludes the budget is fine.
|
|
20
|
+
*
|
|
21
|
+
* Limits are **changed in IAM**, not here. The page says where.
|
|
22
|
+
*
|
|
23
|
+
* @module views/mcp/OrganizationUsage
|
|
24
|
+
*/
|
|
25
|
+
import type { JSX } from 'react';
|
|
26
|
+
import type { McpAgentSpend, McpOrganizationUsage } from '../../api/mcp/organizations';
|
|
27
|
+
import { type McpErrorStateFn } from './types';
|
|
28
|
+
export interface OrganizationUsageProps {
|
|
29
|
+
/** The application's words for a failed request. */
|
|
30
|
+
errorState: McpErrorStateFn;
|
|
31
|
+
/** The organization whose use this is. */
|
|
32
|
+
orgUid: string;
|
|
33
|
+
/** One team of it, rather than all of them. */
|
|
34
|
+
teamUid?: string;
|
|
35
|
+
/** Drawn without its heading, when the page around it carries one. */
|
|
36
|
+
showTitle?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export type SpendRow = McpAgentSpend & {
|
|
39
|
+
id: string;
|
|
40
|
+
};
|
|
41
|
+
/** Two decimals for credits, none for counts. */
|
|
42
|
+
export declare const amount: (value: number) => string;
|
|
43
|
+
/**
|
|
44
|
+
* How close to the limit, said in colour.
|
|
45
|
+
*
|
|
46
|
+
* Three bands rather than a gradient: a reader acts at "nearly there" and
|
|
47
|
+
* not at eighty-one percent, and a bar that reddens smoothly gives them no
|
|
48
|
+
* moment to act on.
|
|
49
|
+
*/
|
|
50
|
+
export declare const quotaTone: (fraction: number) => "success" | "attention" | "danger";
|
|
51
|
+
/**
|
|
52
|
+
* What the breakdown should show: the rows, nothing, or a refusal to guess.
|
|
53
|
+
*
|
|
54
|
+
* `unreadable` is not `empty`. The gateway sends no rows in both cases —
|
|
55
|
+
* because nobody spent anything, or because it could not read the ledger —
|
|
56
|
+
* and drawing them the same way tells a reader "your agents were quiet"
|
|
57
|
+
* when the truth is "we cannot tell you". They act on that differently.
|
|
58
|
+
*/
|
|
59
|
+
export declare const breakdownState: (usage: McpOrganizationUsage | undefined) => "unreadable" | "empty" | "rows";
|
|
60
|
+
/**
|
|
61
|
+
* The rows, keyed the way the gateway grouped them.
|
|
62
|
+
*
|
|
63
|
+
* The agent uid first: service agents reach Runtimes through the gateway and
|
|
64
|
+
* carry *its* client id, so keying on that would collapse an organization's
|
|
65
|
+
* pipelines into one row costing the sum of several.
|
|
66
|
+
*/
|
|
67
|
+
export declare const spendRowsOf: (byAgent: McpAgentSpend[]) => SpendRow[];
|
|
68
|
+
export declare const OrganizationUsage: ({ errorState, orgUid, teamUid, showTitle, }: OrganizationUsageProps) => JSX.Element;
|
|
69
|
+
export default OrganizationUsage;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { Heading, Label, ProgressBar, Spinner, Text } from '@primer/react';
|
|
4
|
+
import { Blankslate, DataTable, Table } from '@primer/react/experimental';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { GraphIcon } from '@primer/octicons-react';
|
|
7
|
+
import { ClientBadge, McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
|
+
import { useOrgMcpUsage } from '../../hooks/useMcp.js';
|
|
9
|
+
/** The quotas, in the order they are read. */
|
|
10
|
+
const QUOTAS = [
|
|
11
|
+
{
|
|
12
|
+
key: 'creditsPerDay',
|
|
13
|
+
label: 'Credits today',
|
|
14
|
+
// Named so the reader knows the window without reading the number: a
|
|
15
|
+
// daily budget and a monthly one differ by thirty, and the mistake is
|
|
16
|
+
// silent.
|
|
17
|
+
hint: 'Against the daily budget',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: 'concurrentSandboxes',
|
|
21
|
+
label: 'Sandboxes running',
|
|
22
|
+
hint: 'Against the concurrency limit',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'callsPerMinute',
|
|
26
|
+
label: 'Calls per minute',
|
|
27
|
+
// The gateway reports this limit without a use, and says why: the
|
|
28
|
+
// windows are per subject, so an organization's "calls this minute" is
|
|
29
|
+
// a sum over subjects that changes while it is being read.
|
|
30
|
+
hint: 'The cap; refusals are where you meet it',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
/** Two decimals for credits, none for counts. */
|
|
34
|
+
export const amount = (value) => Number.isInteger(value) ? String(value) : value.toFixed(2);
|
|
35
|
+
/**
|
|
36
|
+
* How close to the limit, said in colour.
|
|
37
|
+
*
|
|
38
|
+
* Three bands rather than a gradient: a reader acts at "nearly there" and
|
|
39
|
+
* not at eighty-one percent, and a bar that reddens smoothly gives them no
|
|
40
|
+
* moment to act on.
|
|
41
|
+
*/
|
|
42
|
+
export const quotaTone = (fraction) => fraction >= 0.9 ? 'danger' : fraction >= 0.75 ? 'attention' : 'success';
|
|
43
|
+
/**
|
|
44
|
+
* What the breakdown should show: the rows, nothing, or a refusal to guess.
|
|
45
|
+
*
|
|
46
|
+
* `unreadable` is not `empty`. The gateway sends no rows in both cases —
|
|
47
|
+
* because nobody spent anything, or because it could not read the ledger —
|
|
48
|
+
* and drawing them the same way tells a reader "your agents were quiet"
|
|
49
|
+
* when the truth is "we cannot tell you". They act on that differently.
|
|
50
|
+
*/
|
|
51
|
+
export const breakdownState = (usage) => {
|
|
52
|
+
if (!usage || usage.quotas?.creditsPerDay?.unknown) {
|
|
53
|
+
return 'unreadable';
|
|
54
|
+
}
|
|
55
|
+
return (usage.byAgent ?? []).length > 0 ? 'rows' : 'empty';
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The rows, keyed the way the gateway grouped them.
|
|
59
|
+
*
|
|
60
|
+
* The agent uid first: service agents reach Runtimes through the gateway and
|
|
61
|
+
* carry *its* client id, so keying on that would collapse an organization's
|
|
62
|
+
* pipelines into one row costing the sum of several.
|
|
63
|
+
*/
|
|
64
|
+
export const spendRowsOf = (byAgent) => byAgent.map((entry, index) => ({
|
|
65
|
+
...entry,
|
|
66
|
+
id: entry.agentUid || entry.clientId || String(index),
|
|
67
|
+
}));
|
|
68
|
+
const QuotaCard = ({ label, hint, quota, }) => {
|
|
69
|
+
const limit = quota?.limit;
|
|
70
|
+
const used = quota?.used;
|
|
71
|
+
const fraction = quota?.fraction;
|
|
72
|
+
return (_jsxs(Box, { sx: {
|
|
73
|
+
border: '1px solid',
|
|
74
|
+
borderColor: 'border.default',
|
|
75
|
+
borderRadius: 2,
|
|
76
|
+
p: 3,
|
|
77
|
+
flex: '1 1 220px',
|
|
78
|
+
minWidth: 0,
|
|
79
|
+
}, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: label }), _jsx(Box, { sx: { mt: 2, display: 'flex', alignItems: 'baseline', gap: 2 }, children: quota?.unknown ? (_jsx(Text, { sx: { fontSize: 2, color: 'fg.muted' }, children: "Not available" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 4, fontWeight: 'bold' }, children: typeof used === 'number' ? amount(used) : '—' }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: typeof limit === 'number'
|
|
80
|
+
? `of ${amount(limit)}`
|
|
81
|
+
: 'no limit set' })] })) }), typeof fraction === 'number' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(ProgressBar, { progress: Math.min(100, fraction * 100), barSize: "small", bg: `${quotaTone(fraction)}.emphasis`, "aria-label": `${label}: ${Math.round(fraction * 100)}% of the limit` }) })), _jsx(Text, { as: "p", sx: { mt: 2, mb: 0, fontSize: 0, color: 'fg.muted' }, children: quota?.unknown ? quota.unknown : hint })] }));
|
|
82
|
+
};
|
|
83
|
+
export const OrganizationUsage = ({ errorState, orgUid, teamUid, showTitle = true, }) => {
|
|
84
|
+
const usage = useOrgMcpUsage(orgUid, teamUid ? { team: teamUid } : {});
|
|
85
|
+
const rows = useMemo(() => spendRowsOf(usage.data?.byAgent ?? []), [usage.data]);
|
|
86
|
+
if (usage.isLoading) {
|
|
87
|
+
return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 6 }, children: _jsx(Spinner, {}) }));
|
|
88
|
+
}
|
|
89
|
+
if (usage.isError) {
|
|
90
|
+
return (_jsx(McpErrorBlankslate, { state: errorState(usage.error, 'The usage'), onRetry: () => void usage.refetch() }));
|
|
91
|
+
}
|
|
92
|
+
const quotas = usage.data?.quotas ?? {};
|
|
93
|
+
const breakdown = breakdownState(usage.data);
|
|
94
|
+
return (_jsxs(Box, { children: [showTitle && (_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Usage" })), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0, mb: 3 }, children: "What this organization's agents used over the last day, against what they may use. Limits are set in the organization's MCP policy, not here." }), _jsx(Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 3 }, children: QUOTAS.map(quota => (_jsx(QuotaCard, { label: quota.label, hint: quota.hint, quota: quotas[quota.key] }, quota.key))) }), _jsx(Heading, { as: "h3", sx: { fontSize: 2, mt: 4, mb: 2 }, children: "Credits by agent" }), breakdown === 'unreadable' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The day's spend could not be read" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "A breakdown of a total nobody could read would make every agent look cheap. Try again in a moment." }) })] })) : breakdown === 'empty' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent spent credits today" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "Work a person ran themselves is in the total above, and is not an agent's." }) })] })) : (_jsx(Table.Container, { children: _jsx(DataTable, { "aria-labelledby": "usage-by-agent", data: rows, columns: [
|
|
95
|
+
{
|
|
96
|
+
header: 'Agent',
|
|
97
|
+
field: 'clientId',
|
|
98
|
+
rowHeader: true,
|
|
99
|
+
renderCell: row => row.clientId ? (_jsx(ClientBadge, { clientId: row.clientId })) : (_jsx(Label, { variant: "secondary", children: row.agentUid })),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
header: 'Credits',
|
|
103
|
+
field: 'credits',
|
|
104
|
+
renderCell: row => amount(row.credits),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
header: 'Records',
|
|
108
|
+
field: 'records',
|
|
109
|
+
renderCell: row => String(row.records),
|
|
110
|
+
},
|
|
111
|
+
] }) }))] }));
|
|
112
|
+
};
|
|
113
|
+
export default OrganizationUsage;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules a person sets for their own agents.
|
|
3
|
+
*
|
|
4
|
+
* The same six rules as an organization's layer, over the same
|
|
5
|
+
* `PolicyForm` — but they mean something narrower here, and the difference
|
|
6
|
+
* is the whole reason this page needs saying anything at all.
|
|
7
|
+
*
|
|
8
|
+
* **A personal layer only narrows.** Every layer intersects: the platform's
|
|
9
|
+
* defaults, then the organization's, then the team's, then this. Denying a
|
|
10
|
+
* tool here denies it. *Permitting* one an organization denies does nothing
|
|
11
|
+
* — the denial unions across layers and cannot be lifted from below. A cap
|
|
12
|
+
* of 60 under an organization's 30 leaves the effective cap at 30.
|
|
13
|
+
*
|
|
14
|
+
* So the page compares what is typed against the organizations the person
|
|
15
|
+
* belongs to and says, **beside the field while their hands are on it**,
|
|
16
|
+
* where a value will have no effect and why.
|
|
17
|
+
*
|
|
18
|
+
* It does not *refuse* those values, and that is deliberate. The same
|
|
19
|
+
* personal layer applies to personal-scope work, where no organization
|
|
20
|
+
* narrows anything — a rule that is wider than one employer's is still the
|
|
21
|
+
* rule that governs an agent acting for the person alone. Refusing it would
|
|
22
|
+
* make somebody's own policy unwritable because of a place they happen to
|
|
23
|
+
* work.
|
|
24
|
+
*
|
|
25
|
+
* @module views/mcp/PersonalPolicy
|
|
26
|
+
*/
|
|
27
|
+
import type { JSX } from 'react';
|
|
28
|
+
import { type McpPolicyRules } from '../../api/iam/mcpPolicy';
|
|
29
|
+
import type { McpErrorStateFn } from './types';
|
|
30
|
+
/** One organization the person belongs to, and what it narrows. */
|
|
31
|
+
export interface NarrowingOrganization {
|
|
32
|
+
uid: string;
|
|
33
|
+
name: string;
|
|
34
|
+
rules: McpPolicyRules | null;
|
|
35
|
+
}
|
|
36
|
+
export interface PersonalPolicyProps {
|
|
37
|
+
errorState: McpErrorStateFn;
|
|
38
|
+
/** The person whose layer this is. */
|
|
39
|
+
userUid: string;
|
|
40
|
+
/**
|
|
41
|
+
* The organizations they belong to, with each one's layer.
|
|
42
|
+
*
|
|
43
|
+
* Empty is the ordinary case for somebody with no organization, and the
|
|
44
|
+
* page simply says nothing about narrowing.
|
|
45
|
+
*/
|
|
46
|
+
organizations?: NarrowingOrganization[];
|
|
47
|
+
showTitle?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** A list rule already denied elsewhere, as a sentence naming who. */
|
|
50
|
+
export declare const deniedElsewhere: (typed: string, organizations: NarrowingOrganization[]) => string[];
|
|
51
|
+
/** A number rule already capped lower elsewhere. */
|
|
52
|
+
export declare const cappedLower: (typed: string, organizations: NarrowingOrganization[], rule: "maxCallsPerMinute" | "maxCreditsPerDay" | "maxConcurrentSandboxes") => string[];
|
|
53
|
+
export declare const PersonalPolicy: ({ errorState, userUid, organizations, showTitle, }: PersonalPolicyProps) => JSX.Element;
|
|
54
|
+
export default PersonalPolicy;
|