From 5dcd3164761c4afdda6f986b20b5699353d37ad1 Mon Sep 17 00:00:00 2001 From: balaji Date: Tue, 4 Aug 2026 10:03:11 -0700 Subject: [PATCH 1/6] feat(nvcf-ui): import the self-hosted UI into the monorepo Imports ngc/apps/nvcf-ui at 2c6b0b711b46c964cd4b3a119c5dd467540efbfc into src/uis/nvcf-ui, grouped by artifact kind alongside src/clis/nvcf-cli rather than under control-plane services: the UI is a shipped user-facing artifact, not a control-plane service. 267 of the upstream's 271 tracked files come across. Excluded: - .gitlab-ci.yml, superseded by this repository's release path - node_modules/.vite/.../results.json, a committed vitest cache artifact - .vscode/, editor-local settings Rewrites bare uppercase SIS to describe the cluster API in prose instead. All five occurrences were comments or documentation, so nothing structural changed. The OSS snapshot tooling has historically dropped whole files containing those bare tokens, which would have silently removed backend/cmd/server/main.go from the public mirror and broken the build with no error pointing at the cause. Adds /src/uis/ to CODEOWNERS under nvcf-control-plane-dev, per review request. The subtree keeps the upstream MODULE.bazel for now and is listed in .bazelignore, so the root module does not try to load packages whose labels are still relative to the upstream module root. That is deliberately temporary and runs against the recent consolidation of every nested module into the root; merging the JS rules into the root MODULE.bazel and rewriting the labels needs a real build to validate and belongs in its own change. Not included, and tracked in NVIDIA/nvcf#634: - root MODULE.bazel merge and the bazel.yml matrix row - an npm collector for tools/collect-dependencies; until it exists the UI's third-party tree does not reach dependencies.md NOTICE is regenerated to absorb the UI's own NOTICE. All 182 imported source files already carry SPDX headers, and tools/ci/check-license passes. Co-authored-by: Balaji Ganesan --- .bazelignore | 5 + .github/CODEOWNERS | 1 + NOTICE | 1 + src/uis/nvcf-ui/.bazelignore | 25 + src/uis/nvcf-ui/.bazelrc | 41 + src/uis/nvcf-ui/.bazelversion | 1 + .../.claude/skills/add-feature/SKILL.md | 134 + .../nvcf-ui/.claude/skills/codegen/SKILL.md | 51 + src/uis/nvcf-ui/.claude/skills/run/SKILL.md | 37 + .../nvcf-ui/.claude/skills/testing/SKILL.md | 55 + src/uis/nvcf-ui/.coderabbit.yaml | 90 + src/uis/nvcf-ui/.gitignore | 9 + src/uis/nvcf-ui/.security-triage.yaml | 29 + src/uis/nvcf-ui/AGENTS.md | 91 + src/uis/nvcf-ui/BUILD.bazel | 92 + src/uis/nvcf-ui/CLAUDE.md | 1 + src/uis/nvcf-ui/LICENSE | 190 + src/uis/nvcf-ui/MODULE.bazel | 102 + src/uis/nvcf-ui/MODULE.bazel.lock | 4081 ++++++++++++++ src/uis/nvcf-ui/NOTICE | 14 + src/uis/nvcf-ui/README.md | 126 + src/uis/nvcf-ui/SECURITY.md | 195 + src/uis/nvcf-ui/THIRD-PARTY.txt | 624 ++ src/uis/nvcf-ui/Taskfile.yml | 83 + src/uis/nvcf-ui/backend/.gitignore | 2 + src/uis/nvcf-ui/backend/.golangci.yml | 28 + src/uis/nvcf-ui/backend/BUILD.bazel | 36 + src/uis/nvcf-ui/backend/Taskfile.yml | 44 + .../backend/cmd/control-plane/BUILD.bazel | 41 + .../nvcf-ui/backend/cmd/control-plane/main.go | 61 + .../nvcf-ui/backend/cmd/server/BUILD.bazel | 45 + src/uis/nvcf-ui/backend/cmd/server/main.go | 230 + src/uis/nvcf-ui/backend/go.mod | 65 + src/uis/nvcf-ui/backend/go.sum | 180 + .../internal/control-plane/BUILD.bazel | 71 + .../backend/internal/control-plane/handler.go | 83 + .../internal/control-plane/handler_test.go | 144 + .../backend/internal/control-plane/health.go | 341 ++ .../internal/control-plane/health_test.go | 625 +++ .../internal/control-plane/heartbeat.go | 93 + .../internal/control-plane/heartbeat_test.go | 129 + .../internal/control-plane/httphealth.go | 56 + .../internal/control-plane/k8shealth.go | 149 + .../internal/control-plane/k8shealth_test.go | 199 + .../backend/internal/middleware/BUILD.bazel | 44 + .../backend/internal/middleware/middleware.go | 75 + .../internal/middleware/middleware_test.go | 199 + .../internal/token-watcher/BUILD.bazel | 42 + .../backend/internal/token-watcher/watcher.go | 236 + .../internal/token-watcher/watcher_test.go | 444 ++ .../backend/internal/utils/BUILD.bazel | 44 + .../nvcf-ui/backend/internal/utils/utils.go | 68 + .../backend/internal/utils/utils_test.go | 112 + src/uis/nvcf-ui/biome.json | 4 + src/uis/nvcf-ui/go.work.bazel | 22 + src/uis/nvcf-ui/helm/.helmignore | 38 + src/uis/nvcf-ui/helm/Chart.yaml | 26 + src/uis/nvcf-ui/helm/templates/NOTES.txt | 44 + src/uis/nvcf-ui/helm/templates/_helpers.tpl | 171 + .../control-plane-components-configmap.yaml | 27 + .../templates/control-plane-deployment.yaml | 115 + .../helm/templates/control-plane-rbac.yaml | 80 + .../control-plane-serviceaccount.yaml | 28 + .../nvcf-ui/helm/templates/deployment.yaml | 137 + .../templates/hook-openbao-migrations.yaml | 95 + src/uis/nvcf-ui/helm/templates/hpa.yaml | 47 + src/uis/nvcf-ui/helm/templates/httproute.yaml | 57 + src/uis/nvcf-ui/helm/templates/rbac.yaml | 45 + .../helm/templates/referencegrant.yaml | 38 + src/uis/nvcf-ui/helm/templates/service.yaml | 30 + .../helm/templates/serviceaccount.yaml | 28 + .../helm/templates/vault-agent-configmap.yaml | 34 + src/uis/nvcf-ui/helm/values.yaml | 386 ++ src/uis/nvcf-ui/license-header.txt | 14 + src/uis/nvcf-ui/platforms/BUILD.bazel | 37 + src/uis/nvcf-ui/rules/oci/BUILD.bazel | 23 + src/uis/nvcf-ui/rules/oci/defs.bzl | 21 + src/uis/nvcf-ui/rules/oci/private/BUILD.bazel | 22 + src/uis/nvcf-ui/rules/oci/private/common.bzl | 130 + src/uis/nvcf-ui/rules/oci/private/go.bzl | 149 + src/uis/nvcf-ui/rules/oci/transition.bzl | 42 + .../nvcf-ui/spec/control-plane-openapi.yaml | 78 + src/uis/nvcf-ui/spec/nvcf-openapi.yaml | 985 ++++ src/uis/nvcf-ui/spec/sis-openapi.yaml | 366 ++ src/uis/nvcf-ui/tools/workspace_status.sh | 52 + src/uis/nvcf-ui/ui/.gitignore | 15 + src/uis/nvcf-ui/ui/BUILD.bazel | 73 + src/uis/nvcf-ui/ui/Taskfile.yml | 65 + src/uis/nvcf-ui/ui/biome.json | 53 + src/uis/nvcf-ui/ui/index.html | 29 + src/uis/nvcf-ui/ui/orval.config.ts | 369 ++ src/uis/nvcf-ui/ui/package.json | 73 + src/uis/nvcf-ui/ui/pnpm-lock.yaml | 4994 +++++++++++++++++ src/uis/nvcf-ui/ui/pnpm-workspace.yaml | 22 + src/uis/nvcf-ui/ui/public/favicon.ico | Bin 0 -> 25214 bytes .../nvcf-ui/ui/public/mockServiceWorker.js | 366 ++ src/uis/nvcf-ui/ui/scripts/vendor-kui-css.js | 47 + .../ui/src/components/AsyncBoundary.tsx | 47 + .../nvcf-ui/ui/src/components/CodeSnippet.tsx | 62 + .../nvcf-ui/ui/src/components/CopyButton.tsx | 86 + .../nvcf-ui/ui/src/components/CopyText.tsx | 33 + .../components/DataTable/ActiveFilters.tsx | 77 + .../components/DataTable/ColumnVisibility.tsx | 48 + .../ui/src/components/DataTable/Content.tsx | 201 + .../components/DataTable/DataTable.test.tsx | 422 ++ .../ui/src/components/DataTable/DataTable.tsx | 66 + .../components/DataTable/DataTableContext.tsx | 35 + .../ui/src/components/DataTable/Filters.tsx | 44 + .../ui/src/components/DataTable/ItemCount.tsx | 38 + .../src/components/DataTable/Pagination.tsx | 86 + .../ui/src/components/DataTable/Search.tsx | 62 + .../components/DataTable/Skeleton.test.tsx | 50 + .../ui/src/components/DataTable/Skeleton.tsx | 101 + .../ui/src/components/DataTable/Sort.tsx | 100 + .../ui/src/components/DataTable/Toolbar.tsx | 29 + .../ui/src/components/DataTable/index.ts | 49 + .../DataTable/utils/columns.test.ts | 39 + .../src/components/DataTable/utils/columns.ts | 24 + .../DataTable/utils/filter-fns.test.ts | 101 + .../components/DataTable/utils/filter-fns.ts | 47 + .../DataTable/utils/filter-items.test.ts | 111 + .../DataTable/utils/filter-items.ts | 66 + .../src/components/DataTable/utils/index.ts | 20 + .../DataTable/utils/tableSearchSchema.ts | 39 + .../ui/src/components/DetailList.test.tsx | 86 + .../nvcf-ui/ui/src/components/DetailList.tsx | 97 + .../nvcf-ui/ui/src/components/NotFound.tsx | 39 + .../ui/src/components/OverflowGroup.test.tsx | 77 + .../ui/src/components/OverflowGroup.tsx | 179 + .../ui/src/components/RouteErrorFallback.tsx | 41 + .../ui/src/components/RouteSpinner.tsx | 27 + src/uis/nvcf-ui/ui/src/config/.gitkeep | 0 .../components/AccountSwitcher.test.tsx | 116 + .../accounts/components/AccountSwitcher.tsx | 58 + .../components/AccountSwitcherSkeleton.tsx | 29 + .../src/features/accounts/getActiveNcaId.ts | 31 + .../accounts/hooks/useActiveAccount.ts | 78 + .../features/clusters/ClusterDetails.test.tsx | 162 + .../src/features/clusters/ClusterDetails.tsx | 237 + .../features/clusters/ClustersList.test.tsx | 57 + .../ui/src/features/clusters/ClustersList.tsx | 131 + .../features/clusters/ClustersListPending.tsx | 28 + .../components/ClustersPageHeading.tsx | 33 + .../components/GpuUtilization.test.tsx | 39 + .../clusters/components/GpuUtilization.tsx | 52 + .../clusters/components/StatusBadge.test.tsx | 31 + .../clusters/components/StatusBadge.tsx | 80 + .../ui/src/features/clusters/constants.ts | 20 + .../ui/src/features/clusters/routes.tsx | 65 + .../src/features/dashboard/Dashboard.test.tsx | 514 ++ .../ui/src/features/dashboard/Dashboard.tsx | 100 + .../components/ClusterStatsPanel.tsx | 147 + .../components/ControlPlaneStatsPanel.tsx | 115 + .../components/FunctionStatsPanel.tsx | 89 + .../dashboard/components/PageHeading.tsx | 44 + .../components/ReadyClustersPanel.tsx | 185 + .../components/RecentFunctionsList.tsx | 176 + .../components/StatPanelBoundary.tsx | 64 + .../components/StatPanelSkeleton.tsx | 48 + .../ui/src/features/dashboard/routes.tsx | 54 + .../functions/FunctionDetail.test.tsx | 164 + .../src/features/functions/FunctionDetail.tsx | 439 ++ .../features/functions/FunctionsList.test.tsx | 81 + .../src/features/functions/FunctionsList.tsx | 125 + .../functions/FunctionsListPending.tsx | 36 + .../components/FunctionCard.test.tsx | 91 + .../functions/components/FunctionCard.tsx | 168 + .../components/FunctionsPageHeading.tsx | 33 + .../functions/components/StatusBadge.tsx | 58 + .../ui/src/features/functions/constants.ts | 33 + .../hooks/useFunctionsWithDeployments.ts | 80 + .../ui/src/features/functions/routes.tsx | 95 + .../ui/src/features/functions/types/index.ts | 25 + .../src/generated/api/account/account.msw.ts | 70 + .../ui/src/generated/api/account/account.ts | 324 ++ .../generated/api/clusters/clusters.msw.ts | 523 ++ .../ui/src/generated/api/clusters/clusters.ts | 695 +++ .../api/control-plane/control-plane.msw.ts | 76 + .../api/control-plane/control-plane.ts | 322 ++ .../function-deployment.msw.ts | 243 + .../function-deployment.ts | 704 +++ .../function-management.msw.ts | 547 ++ .../function-management.ts | 765 +++ .../ui/src/generated/model/accountDto.ts | 48 + .../ui/src/generated/model/artifactDto.ts | 35 + .../model/autoscalingConfigurationDto.ts | 34 + .../model/containerEnvironmentEntryDto.ts | 33 + .../model/controlPlaneComponentStatus.ts | 39 + .../controlPlaneComponentStatusStatus.ts | 35 + .../generated/model/deploymentHealthDto.ts | 52 + .../src/generated/model/deploymentResponse.ts | 32 + .../generated/model/functionDeploymentDto.ts | 78 + .../functionDeploymentDtoFunctionStatus.ts | 39 + .../ui/src/generated/model/functionDto.ts | 126 + .../model/functionDtoApiBodyFormat.ts | 34 + .../model/functionDtoFunctionType.ts | 35 + .../src/generated/model/functionDtoStatus.ts | 39 + .../src/generated/model/functionModelDto.ts | 38 + .../src/generated/model/functionResponse.ts | 32 + .../generated/model/getAllFunctionsParams.ts | 34 + .../model/getAllFunctionsVisibilityItem.ts | 32 + .../src/generated/model/getClusterResponse.ts | 91 + .../model/getClusterResponseCloudProvider.ts | 41 + ...lusterResponseClusterConfigurationFiles.ts | 30 + ...getClusterResponseClusterConfigurations.ts | 28 + .../getClusterResponseClusterUpgradeStatus.ts | 37 + .../model/getClusterResponseGpuUsage.ts | 29 + .../model/getClusterResponseStatus.ts | 41 + .../src/generated/model/getClustersParams.ts | 34 + .../model/getFunctionVersionParams.ts | 32 + .../ui/src/generated/model/gpuCapacity.ts | 29 + .../src/generated/model/gpuResponseSchema.ts | 36 + .../generated/model/gpuSpecificationDto.ts | 105 + .../model/gpuSpecificationDtoConfiguration.ts | 30 + .../ui/src/generated/model/healthDto.ts | 40 + .../src/generated/model/healthDtoProtocol.ts | 34 + .../model/helmValidationPolicyDto.ts | 41 + .../model/helmValidationPolicyDtoName.ts | 34 + .../ui/src/generated/model/imageConfig.ts | 39 + .../generated/model/imageCredentialHelper.ts | 32 + .../ui/src/generated/model/instanceDto.ts | 66 + .../model/instanceDtoInstanceStatus.ts | 39 + .../model/instanceTypeResponseSchema.ts | 56 + .../ui/src/generated/model/kubernetesType.ts | 41 + .../generated/model/listAccountResponse.ts | 35 + .../model/listDeploymentsResponse.ts | 35 + .../generated/model/listFunctionsResponse.ts | 35 + .../ui/src/generated/model/llmConfigDto.ts | 40 + .../ui/src/generated/model/rateLimitDto.ts | 50 + .../model/rateLimitDtoPerNcaIdRate.ts | 28 + .../ui/src/generated/model/scalingDetails.ts | 35 + .../ui/src/generated/model/sisConfig.ts | 35 + .../src/generated/model/stickinessWindow.ts | 30 + .../ui/src/generated/model/telemetriesDto.ts | 47 + .../ui/src/generated/model/vaultConfig.ts | 31 + .../ui/src/hooks/useLocalStorage.test.ts | 77 + .../nvcf-ui/ui/src/hooks/useLocalStorage.ts | 72 + .../ui/src/hooks/useTableUrlSync.test.ts | 92 + .../nvcf-ui/ui/src/hooks/useTableUrlSync.ts | 129 + .../ui/src/hooks/useThemePreference.test.tsx | 70 + .../ui/src/hooks/useThemePreference.ts | 57 + src/uis/nvcf-ui/ui/src/lib/fetch.test.ts | 69 + src/uis/nvcf-ui/ui/src/lib/fetch.ts | 43 + src/uis/nvcf-ui/ui/src/lib/queryClient.ts | 32 + src/uis/nvcf-ui/ui/src/main.tsx | 60 + src/uis/nvcf-ui/ui/src/mocks/browser.ts | 24 + src/uis/nvcf-ui/ui/src/mocks/handlers.ts | 57 + .../mocks/scenarios/functions/all-statuses.ts | 42 + .../mocks/scenarios/functions/empty-list.ts | 20 + .../scenarios/functions/no-deployment.ts | 31 + src/uis/nvcf-ui/ui/src/mocks/server.ts | 21 + .../nvcf-ui/ui/src/mocks/store/handlers.ts | 93 + src/uis/nvcf-ui/ui/src/mocks/store/index.ts | 19 + src/uis/nvcf-ui/ui/src/mocks/store/store.ts | 113 + src/uis/nvcf-ui/ui/src/rootRoute.tsx | 119 + src/uis/nvcf-ui/ui/src/router.tsx | 71 + src/uis/nvcf-ui/ui/src/styles.css | 55 + src/uis/nvcf-ui/ui/src/testing/render.tsx | 74 + src/uis/nvcf-ui/ui/src/types/table.d.ts | 32 + .../nvcf-ui/ui/src/utils/clipboard.test.ts | 46 + src/uis/nvcf-ui/ui/src/utils/clipboard.ts | 43 + .../nvcf-ui/ui/src/utils/formatters.test.ts | 28 + src/uis/nvcf-ui/ui/src/utils/formatters.ts | 47 + .../nvcf-ui/ui/src/utils/resize-observer.ts | 59 + src/uis/nvcf-ui/ui/tsconfig.json | 26 + .../ui/vendor/kui-foundations/.version | 1 + .../vendor/kui-foundations/base-external.css | 21 + .../ui/vendor/kui-foundations/components.css | 18 + src/uis/nvcf-ui/ui/vite.config.ts | 78 + src/uis/nvcf-ui/ui/vitest.setup.ts | 29 + 270 files changed, 34599 insertions(+) create mode 100644 src/uis/nvcf-ui/.bazelignore create mode 100644 src/uis/nvcf-ui/.bazelrc create mode 100644 src/uis/nvcf-ui/.bazelversion create mode 100644 src/uis/nvcf-ui/.claude/skills/add-feature/SKILL.md create mode 100644 src/uis/nvcf-ui/.claude/skills/codegen/SKILL.md create mode 100644 src/uis/nvcf-ui/.claude/skills/run/SKILL.md create mode 100644 src/uis/nvcf-ui/.claude/skills/testing/SKILL.md create mode 100644 src/uis/nvcf-ui/.coderabbit.yaml create mode 100644 src/uis/nvcf-ui/.gitignore create mode 100644 src/uis/nvcf-ui/.security-triage.yaml create mode 100644 src/uis/nvcf-ui/AGENTS.md create mode 100644 src/uis/nvcf-ui/BUILD.bazel create mode 100644 src/uis/nvcf-ui/CLAUDE.md create mode 100644 src/uis/nvcf-ui/LICENSE create mode 100644 src/uis/nvcf-ui/MODULE.bazel create mode 100644 src/uis/nvcf-ui/MODULE.bazel.lock create mode 100644 src/uis/nvcf-ui/NOTICE create mode 100644 src/uis/nvcf-ui/README.md create mode 100644 src/uis/nvcf-ui/SECURITY.md create mode 100644 src/uis/nvcf-ui/THIRD-PARTY.txt create mode 100644 src/uis/nvcf-ui/Taskfile.yml create mode 100644 src/uis/nvcf-ui/backend/.gitignore create mode 100644 src/uis/nvcf-ui/backend/.golangci.yml create mode 100644 src/uis/nvcf-ui/backend/BUILD.bazel create mode 100644 src/uis/nvcf-ui/backend/Taskfile.yml create mode 100644 src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel create mode 100644 src/uis/nvcf-ui/backend/cmd/control-plane/main.go create mode 100644 src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel create mode 100644 src/uis/nvcf-ui/backend/cmd/server/main.go create mode 100644 src/uis/nvcf-ui/backend/go.mod create mode 100644 src/uis/nvcf-ui/backend/go.sum create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/handler.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/handler_test.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/health.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/health_test.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/heartbeat.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/heartbeat_test.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/httphealth.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/k8shealth.go create mode 100644 src/uis/nvcf-ui/backend/internal/control-plane/k8shealth_test.go create mode 100644 src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel create mode 100644 src/uis/nvcf-ui/backend/internal/middleware/middleware.go create mode 100644 src/uis/nvcf-ui/backend/internal/middleware/middleware_test.go create mode 100644 src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel create mode 100644 src/uis/nvcf-ui/backend/internal/token-watcher/watcher.go create mode 100644 src/uis/nvcf-ui/backend/internal/token-watcher/watcher_test.go create mode 100644 src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel create mode 100644 src/uis/nvcf-ui/backend/internal/utils/utils.go create mode 100644 src/uis/nvcf-ui/backend/internal/utils/utils_test.go create mode 100644 src/uis/nvcf-ui/biome.json create mode 100644 src/uis/nvcf-ui/go.work.bazel create mode 100644 src/uis/nvcf-ui/helm/.helmignore create mode 100644 src/uis/nvcf-ui/helm/Chart.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/NOTES.txt create mode 100644 src/uis/nvcf-ui/helm/templates/_helpers.tpl create mode 100644 src/uis/nvcf-ui/helm/templates/control-plane-components-configmap.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/control-plane-deployment.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/control-plane-rbac.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/control-plane-serviceaccount.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/deployment.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/hook-openbao-migrations.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/hpa.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/httproute.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/rbac.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/referencegrant.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/service.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/serviceaccount.yaml create mode 100644 src/uis/nvcf-ui/helm/templates/vault-agent-configmap.yaml create mode 100644 src/uis/nvcf-ui/helm/values.yaml create mode 100644 src/uis/nvcf-ui/license-header.txt create mode 100644 src/uis/nvcf-ui/platforms/BUILD.bazel create mode 100644 src/uis/nvcf-ui/rules/oci/BUILD.bazel create mode 100644 src/uis/nvcf-ui/rules/oci/defs.bzl create mode 100644 src/uis/nvcf-ui/rules/oci/private/BUILD.bazel create mode 100644 src/uis/nvcf-ui/rules/oci/private/common.bzl create mode 100644 src/uis/nvcf-ui/rules/oci/private/go.bzl create mode 100644 src/uis/nvcf-ui/rules/oci/transition.bzl create mode 100644 src/uis/nvcf-ui/spec/control-plane-openapi.yaml create mode 100644 src/uis/nvcf-ui/spec/nvcf-openapi.yaml create mode 100644 src/uis/nvcf-ui/spec/sis-openapi.yaml create mode 100755 src/uis/nvcf-ui/tools/workspace_status.sh create mode 100644 src/uis/nvcf-ui/ui/.gitignore create mode 100644 src/uis/nvcf-ui/ui/BUILD.bazel create mode 100644 src/uis/nvcf-ui/ui/Taskfile.yml create mode 100644 src/uis/nvcf-ui/ui/biome.json create mode 100644 src/uis/nvcf-ui/ui/index.html create mode 100644 src/uis/nvcf-ui/ui/orval.config.ts create mode 100644 src/uis/nvcf-ui/ui/package.json create mode 100644 src/uis/nvcf-ui/ui/pnpm-lock.yaml create mode 100644 src/uis/nvcf-ui/ui/pnpm-workspace.yaml create mode 100644 src/uis/nvcf-ui/ui/public/favicon.ico create mode 100644 src/uis/nvcf-ui/ui/public/mockServiceWorker.js create mode 100644 src/uis/nvcf-ui/ui/scripts/vendor-kui-css.js create mode 100644 src/uis/nvcf-ui/ui/src/components/AsyncBoundary.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/CodeSnippet.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/CopyButton.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/CopyText.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/ActiveFilters.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/ColumnVisibility.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Content.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/DataTable.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/DataTable.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/DataTableContext.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Filters.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/ItemCount.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Pagination.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Search.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Skeleton.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Skeleton.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Sort.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/Toolbar.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/index.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/columns.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/columns.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/filter-fns.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/filter-fns.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/filter-items.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/filter-items.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/index.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DataTable/utils/tableSearchSchema.ts create mode 100644 src/uis/nvcf-ui/ui/src/components/DetailList.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/DetailList.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/NotFound.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/OverflowGroup.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/OverflowGroup.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/RouteErrorFallback.tsx create mode 100644 src/uis/nvcf-ui/ui/src/components/RouteSpinner.tsx create mode 100644 src/uis/nvcf-ui/ui/src/config/.gitkeep create mode 100644 src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcherSkeleton.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/accounts/getActiveNcaId.ts create mode 100644 src/uis/nvcf-ui/ui/src/features/accounts/hooks/useActiveAccount.ts create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/ClustersListPending.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/components/ClustersPageHeading.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/constants.ts create mode 100644 src/uis/nvcf-ui/ui/src/features/clusters/routes.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/ClusterStatsPanel.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/ControlPlaneStatsPanel.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/FunctionStatsPanel.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/PageHeading.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/ReadyClustersPanel.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/RecentFunctionsList.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelBoundary.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelSkeleton.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/dashboard/routes.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/FunctionsListPending.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/components/FunctionsPageHeading.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/components/StatusBadge.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/constants.ts create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/hooks/useFunctionsWithDeployments.ts create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/routes.tsx create mode 100644 src/uis/nvcf-ui/ui/src/features/functions/types/index.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/account/account.msw.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/account/account.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.msw.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.msw.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.msw.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.msw.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/accountDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/artifactDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/autoscalingConfigurationDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/containerEnvironmentEntryDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatus.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatusStatus.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/deploymentHealthDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/deploymentResponse.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDtoFunctionStatus.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionDtoApiBodyFormat.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionDtoFunctionType.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionDtoStatus.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionModelDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/functionResponse.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsParams.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsVisibilityItem.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClusterResponse.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseCloudProvider.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurationFiles.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurations.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterUpgradeStatus.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseGpuUsage.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseStatus.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getClustersParams.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/getFunctionVersionParams.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/gpuCapacity.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/gpuResponseSchema.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDtoConfiguration.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/healthDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/healthDtoProtocol.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDtoName.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/imageConfig.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/imageCredentialHelper.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/instanceDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/instanceDtoInstanceStatus.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/instanceTypeResponseSchema.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/kubernetesType.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/listAccountResponse.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/listDeploymentsResponse.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/listFunctionsResponse.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/llmConfigDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/rateLimitDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/rateLimitDtoPerNcaIdRate.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/scalingDetails.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/sisConfig.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/stickinessWindow.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/telemetriesDto.ts create mode 100644 src/uis/nvcf-ui/ui/src/generated/model/vaultConfig.ts create mode 100644 src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.ts create mode 100644 src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.ts create mode 100644 src/uis/nvcf-ui/ui/src/hooks/useThemePreference.test.tsx create mode 100644 src/uis/nvcf-ui/ui/src/hooks/useThemePreference.ts create mode 100644 src/uis/nvcf-ui/ui/src/lib/fetch.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/lib/fetch.ts create mode 100644 src/uis/nvcf-ui/ui/src/lib/queryClient.ts create mode 100644 src/uis/nvcf-ui/ui/src/main.tsx create mode 100644 src/uis/nvcf-ui/ui/src/mocks/browser.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/handlers.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/all-statuses.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/empty-list.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/no-deployment.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/server.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/store/handlers.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/store/index.ts create mode 100644 src/uis/nvcf-ui/ui/src/mocks/store/store.ts create mode 100644 src/uis/nvcf-ui/ui/src/rootRoute.tsx create mode 100644 src/uis/nvcf-ui/ui/src/router.tsx create mode 100644 src/uis/nvcf-ui/ui/src/styles.css create mode 100644 src/uis/nvcf-ui/ui/src/testing/render.tsx create mode 100644 src/uis/nvcf-ui/ui/src/types/table.d.ts create mode 100644 src/uis/nvcf-ui/ui/src/utils/clipboard.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/utils/clipboard.ts create mode 100644 src/uis/nvcf-ui/ui/src/utils/formatters.test.ts create mode 100644 src/uis/nvcf-ui/ui/src/utils/formatters.ts create mode 100644 src/uis/nvcf-ui/ui/src/utils/resize-observer.ts create mode 100644 src/uis/nvcf-ui/ui/tsconfig.json create mode 100644 src/uis/nvcf-ui/ui/vendor/kui-foundations/.version create mode 100644 src/uis/nvcf-ui/ui/vendor/kui-foundations/base-external.css create mode 100644 src/uis/nvcf-ui/ui/vendor/kui-foundations/components.css create mode 100644 src/uis/nvcf-ui/ui/vite.config.ts create mode 100644 src/uis/nvcf-ui/ui/vitest.setup.ts diff --git a/.bazelignore b/.bazelignore index 64e1389cb..32dee88fc 100644 --- a/.bazelignore +++ b/.bazelignore @@ -45,6 +45,11 @@ src/libraries/python # @stargate_crates from the nested module) do not resolve against the # umbrella root MODULE.bazel. src/libraries/ts +# nvcf-ui is imported with the upstream repo's own MODULE.bazel still in +# place (aspect_rules_js + rules_nodejs). Its BUILD files use labels +# relative to that module root, so the root module keeps it ignored until +# the labels are rewritten and the JS rules are merged into the root. +src/uis/nvcf-ui deploy docs fern diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0f9319485..14a1b57f1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -30,6 +30,7 @@ # ledger, deployment stages, NVCT API, vanity gateway, autoscaler, Helm ReVal, # LLM gateway, LLM request router, SIS, and Stargate. /src/control-plane-services/ @NVIDIA/nvcf-control-plane-dev +/src/uis/ @NVIDIA/nvcf-control-plane-dev /src/invocation-plane-services/ @NVIDIA/nvcf-control-plane-dev /src/libraries/rust/stargate/ @NVIDIA/nvcf-control-plane-dev /deploy/helm/admin-token-issuer-proxy/ @NVIDIA/nvcf-control-plane-dev diff --git a/NOTICE b/NOTICE index 44db63899..3c3ff0586 100644 --- a/NOTICE +++ b/NOTICE @@ -480,3 +480,4 @@ The following third-party licenses are included in this repository: src/libraries/go/lib/vendor/sigs.k8s.io/structured-merge-diff/v6/LICENSE src/libraries/go/lib/vendor/sigs.k8s.io/yaml/LICENSE src/libraries/java/nv-boot-parent/NOTICE + src/uis/nvcf-ui/NOTICE diff --git a/src/uis/nvcf-ui/.bazelignore b/src/uis/nvcf-ui/.bazelignore new file mode 100644 index 000000000..a2fd38205 --- /dev/null +++ b/src/uis/nvcf-ui/.bazelignore @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Mirrors the gazelle:exclude entries in the root BUILD.bazel (needed because +# build //... / query load packages without consulting BUILD directives). + +ui/node_modules +ui/dist +ui/coverage +backend/static +backend/bin +node_modules +.bazel-cache diff --git a/src/uis/nvcf-ui/.bazelrc b/src/uis/nvcf-ui/.bazelrc new file mode 100644 index 000000000..daee3ceaa --- /dev/null +++ b/src/uis/nvcf-ui/.bazelrc @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Per-user overrides (not checked in). +try-import %workspace%/user.bazelrc +try-import %user.home%/.bazelrc.user + +build --compilation_mode=fastbuild +common --enable_bzlmod +build --incompatible_strict_action_env +common --enable_platform_specific_config + +# OCI tag stamping. Off by default; CI opts in with --stamp / --config=ci. +build --workspace_status_command=./tools/workspace_status.sh +build --nostamp + +test --test_output=errors +# Go code needs HOME; the sandbox strips it. Point it at /tmp. +test --test_env=HOME=/tmp +test --test_env=XDG_CACHE_HOME=/tmp/.cache + +startup --host_jvm_args=-Xmx4g + +build --@rules_go//go/config:pure + +build:ci --jobs=32 +build:ci --stamp + +# Remote cache is opt-in; add a build:remote profile to user.bazelrc. diff --git a/src/uis/nvcf-ui/.bazelversion b/src/uis/nvcf-ui/.bazelversion new file mode 100644 index 000000000..acd405b1d --- /dev/null +++ b/src/uis/nvcf-ui/.bazelversion @@ -0,0 +1 @@ +8.6.0 diff --git a/src/uis/nvcf-ui/.claude/skills/add-feature/SKILL.md b/src/uis/nvcf-ui/.claude/skills/add-feature/SKILL.md new file mode 100644 index 000000000..d33839380 --- /dev/null +++ b/src/uis/nvcf-ui/.claude/skills/add-feature/SKILL.md @@ -0,0 +1,134 @@ +--- +name: add-feature +description: > + Add a new UI feature to the nvcf-ui app — folder structure, routing, data loading, + lazy-loading, testing, and completion checklist. Use when creating a new feature folder, + adding a page or view, wiring routes, building a new screen, or deciding whether to extend + an existing feature vs create a new one. Also use when the user asks to build something new + in the UI, even if they don't say "feature" explicitly. +license: Apache-2.0 +allowed-tools: + - Bash + - Read + - Edit +--- + +# Adding a UI Feature + +For all component selection, layout decisions, and styling — invoke the `kaizen-ui` skill. + +## Gotchas + +- This project uses **code-based routing**, not file-based. Do not use `createFileRoute` or the TanStack Router Vite plugin — use `createRoute` and `createLazyRoute`. +- View components are named by view type (e.g. `WidgetsList`, `WidgetDetail`), never with a `Page` suffix. +- `router.tsx` is pure composition — it only imports and wires route trees. Do not put route definitions or loaders in `router.tsx`. +- `rootRoute.tsx` is separate from `router.tsx` to avoid circular imports. Layout routes that span multiple features live at `src/` root, not in a feature folder. +- Generated hooks, schemas, and mocks live in `ui/src/generated/` — never edit these files. If the endpoint you need doesn't have generated hooks, update the appropriate spec in `spec/` (`control-plane-openapi.yaml`, `nvcf-openapi.yaml`, or `sis-openapi.yaml`) then run `task generate`. +- All source files need Apache 2.0 license headers. Run `task license:add` before committing. + +## When to Create a New Feature Folder + +- **New domain** (new data, new concepts, new API surface) → new feature folder +- **Composition of existing domains** (e.g. a home page assembling widgets) → new thin feature folder +- **Extension of an existing domain** (e.g. a "Create Function" page) → new route/component in the existing feature folder + +## Feature Folder Structure + +A feature is a **domain boundary**, not a route. Each feature is self-contained under `ui/src/features//`: + +``` +features// +├── components/ # Smaller building blocks (badges, cards) +├── hooks/ # Feature-scoped hooks +├── types/ # Feature-scoped types +├── routes.tsx # Route definitions, loaders, search validation (optional) +├── .tsx # Route-level view components (PascalCase, named by view type) +└── utils.ts # Feature-scoped helpers (when needed) +``` + +- Not every subfolder is required — add `components/`, `hooks/`, `types/` only when needed +- A feature may not have routes at all — it may only export hooks and components for other features +- Helpers go inline in the component file by default. Extract to a feature-root file only when shared across multiple files in the same feature + +## Route Splitting Pattern + +Each route is split across two files: + +**`routes.tsx`** — route definition, search params, loader. In the main bundle. + +```tsx +import { createRoute } from "@tanstack/react-router"; +import { appLayoutRoute } from "~/appLayout"; +import { queryClient } from "~/lib/queryClient"; +import { getGetWidgetsQueryOptions } from "~/generated/api/widgets/widgets"; +import { GetWidgetsParams } from "~/generated/model/getWidgetsParams.zod"; + +const widgetsRoute = createRoute({ + getParentRoute: () => appLayoutRoute, + path: "widgets", + validateSearch: GetWidgetsParams.catch({ status: "active" }), + loaderDeps: ({ search }) => search, + loader: ({ deps }) => + queryClient.ensureQueryData({ + ...getGetWidgetsQueryOptions(deps), + revalidateIfStale: true, + }), +}).lazy(() => import("./WidgetsList").then((m) => m.WidgetsListRoute)); + +export const widgetsRouteTree = widgetsRoute; +``` + +- Use generated Zod schemas for `validateSearch` when params map to API query params. For UI-only search params, define inline. +- `loaderDeps` declares which search params the loader depends on — the loader re-runs when these change. +- `ensureQueryData` with `revalidateIfStale: true` avoids flashing stale content. +- For multiple data sources: `ensureQueryData` (awaited) for primary data, `prefetchQuery` (no await) for below-the-fold data. + +**`.tsx`** — lazy-loaded component, pending, and error states. + +```tsx +import { createLazyRoute } from "@tanstack/react-router"; +import { useGetWidgetsSuspense } from "~/generated/api/widgets/widgets"; + +function WidgetsList() { + const { status } = widgetsRoute.useSearch(); + const { data } = useGetWidgetsSuspense({ status }); + // render... +} + +export const WidgetsListRoute = createLazyRoute("/widgets")({ + component: WidgetsList, + pendingComponent: () =>
Loading...
, +}); +``` + +- Use suspense hook variants (`useGetWidgetsSuspense`) — the loader already ensured data, so these resolve immediately or suspend if cache is stale. +- For prefetched (non-blocking) data, wrap the consumer in a `` boundary with a fallback. + +## Lazy-Loading Rules + +- Page routes with view code → always lazy-load via `createLazyRoute` +- Layout routes that just render `` → keep in main bundle +- Components within a page → never lazy-load; already in the route's chunk + +## Layout Routes + +- Single feature → in that feature's `routes.tsx` +- Multiple features → at `src/` root alongside `rootRoute.tsx` + +**Tabbed views**: use a layout route with ``, each tab is a child route with its own loader and lazy-loaded component. + +## Steps + +1. Check for existing components/patterns that could be reused or graduated before building new ones +2. Create `features//` with the relevant files — a view component and `routes.tsx` if the feature has routes, or just `components/`/`hooks/` if routeless +3. If the feature has routes: define routes in `routes.tsx` using `createRoute` with `.lazy()`, then wire the subtree into `router.tsx`. If it introduces a new top-level route, add a `NavItem` entry in `rootRoute.tsx` +4. Use generated hooks and query options for data fetching +5. Add tests for view components and any non-trivial logic. Use `renderWithRouter` from `~/testing/render` for route-level views, standard `render` for standalone components +6. Add MSW scenarios in `mocks/scenarios//` for key UI states +7. Run `task license:add` to apply license headers to new files + +## Validate Before Finishing + +1. Run `task ui:lint:fix` and fix any errors +2. Run `task ui:test` and verify tests pass +3. If either fails, fix the issues and rerun until clean diff --git a/src/uis/nvcf-ui/.claude/skills/codegen/SKILL.md b/src/uis/nvcf-ui/.claude/skills/codegen/SKILL.md new file mode 100644 index 000000000..5b2a7924d --- /dev/null +++ b/src/uis/nvcf-ui/.claude/skills/codegen/SKILL.md @@ -0,0 +1,51 @@ +--- +name: codegen +description: > + Run and manage the Orval code generation pipeline for API hooks, TypeScript + models, and MSW mocks. Use when editing an OpenAPI spec, running task generate, + adding or modifying MSW scenarios, troubleshooting generated code, or when + generated hooks are missing or out of date. +license: Apache-2.0 +allowed-tools: + - Bash + - Read +--- + +# Code Generation Pipeline + +`task generate` runs **Orval**, which reads the three OpenAPI specs in `spec/` +directly and generates React Query hooks, TypeScript models, and MSW handler +factories into `ui/src/generated/`. It then adds Apache license headers to the +output. There is no bundling step (no Redocly, no `openapi.yaml`). + +Specs: + +- `spec/control-plane-openapi.yaml` — the UI shim/BFF API +- `spec/nvcf-openapi.yaml` — NVCF Cloud Functions (v2) API +- `spec/sis-openapi.yaml` — cluster API + +## Gotchas + +- Edit the specs in `spec/` — never edit anything in `ui/src/generated/` (regenerated output). +- `ui/src/generated/` is committed and enforced by the **Codegen Check** CI job: if you change a spec, run `task generate` and commit the regenerated output, or CI fails on the diff. +- Orval config is in `ui/orval.config.ts` (one shared `output` block spread across all three specs). Customize generation there, not in the generated files. +- Generated hook names follow Orval's convention: `useGetFunctions`, `useGetFunctionsSuspense`, `getGetFunctionsQueryOptions`. The double "Get" is expected — don't rename them. + +## What Gets Generated + +- **React Query hooks** — `useGetFunctions`, `useGetFunctionsSuspense`, etc. (suspense variants are generated globally). +- **Query options factories** — `getGetFunctionsQueryOptions` for use in route loaders. +- **TypeScript models** — plain `interface`/`type` files in `ui/src/generated/model/` (no runtime validation). Zod generation is intentionally off; re-add it as a separate `client: "zod"` Orval target if runtime validation is ever needed. +- **MSW handler factories** — per-endpoint mock handlers plus per-response mock generators (`getGetFunctionVersionResponseMock`, etc.). + +## Mocks and Scenarios + +The default mock baseline is a **seeded in-memory store** (`ui/src/mocks/store/`), not the raw generated handlers. The store builds a stable dataset once (keyed by account `ncaId`) using the generated response-mock generators, so list and detail views stay consistent and their IDs line up. + +Scenarios go in `ui/src/mocks/scenarios//.ts`, each exporting `{ handlers: HttpHandler[] }`. They **layer on top of** the store baseline (first match wins) — activate with `VITE_SCENARIO=functions:empty-list` (comma-separated for multiple). Focus scenarios on states with a cascading effect on the view (empty list, error, a status that changes available actions) — not minor value variations, which the seeded store already covers. + +## Validate After Running + +1. Run `task generate` +2. Run `task ui:lint:fix` so generated output passes linting/formatting +3. Run `task typecheck` to confirm consumers still type-check against the regenerated models diff --git a/src/uis/nvcf-ui/.claude/skills/run/SKILL.md b/src/uis/nvcf-ui/.claude/skills/run/SKILL.md new file mode 100644 index 000000000..a48fe0b7c --- /dev/null +++ b/src/uis/nvcf-ui/.claude/skills/run/SKILL.md @@ -0,0 +1,37 @@ +--- +name: run +description: > + Start the nvcf-ui development server. Use when asked to run, start, or launch the app, + or to verify a change works in the browser. +license: Apache-2.0 +allowed-tools: + - Bash +--- + +# Running nvcf-ui + +There is no local backend — the app either runs against a real cluster or uses MSW mocks. + +## Mocked mode (default for local dev) + +```bash +VITE_MOCK=true task dev +``` + +Starts Vite at `http://localhost:5173` with MSW intercepting all API requests using the seeded in-memory store. No cluster required. Use this for UI development. + +To layer a scenario on top of the baseline store: + +```bash +VITE_MOCK=true VITE_SCENARIO=functions:empty-list task dev +``` + +Multiple scenarios are comma-separated: `VITE_SCENARIO=functions:empty-list,tasks:launching-task`. + +## Against a real cluster + +```bash +task dev +``` + +No `VITE_MOCK` — requests proxy to the configured upstream. Requires cluster access. Check `ui/vite.config.ts` for the proxy target. diff --git a/src/uis/nvcf-ui/.claude/skills/testing/SKILL.md b/src/uis/nvcf-ui/.claude/skills/testing/SKILL.md new file mode 100644 index 000000000..abab04cc5 --- /dev/null +++ b/src/uis/nvcf-ui/.claude/skills/testing/SKILL.md @@ -0,0 +1,55 @@ +--- +name: testing +description: > + Writing frontend tests for the nvcf-ui app — Vitest, Testing Library, MSW, and + project-specific render helpers. Use when creating test files, writing component tests, + setting up MSW handlers in tests, deciding which render helper to use, or when the user + is adding or modifying a UI component and hasn't mentioned tests yet (tests are required). + Covers TypeScript/React only — Go backend tests follow standard Go patterns. +license: Apache-2.0 +allowed-tools: + - Bash + - Read + - Edit +--- + +# Testing + +Tests are not optional — when adding or modifying a component, add or update the corresponding tests. + +## Gotchas + +- Route-level views that use `useSearch`, `useParams`, or loaders **must** use `renderWithRouter` from `~/testing/render`, not the standard `render`. Using standard `render` will fail because route hooks need router context. +- MSW handlers from scenarios should be reused in tests — don't duplicate handler logic. Import from `~/mocks/scenarios/`. +- Test files live alongside the code they test (`WidgetsList.test.tsx` next to `WidgetsList.tsx`), not in a separate `__tests__` directory. + +## Which Render Helper to Use + +- **Route-level views** → `renderWithRouter` from `~/testing/render` (sets up router, query client, memory history) +- **Standalone components** (no route dependency) → standard `render` from `@testing-library/react` +- **Pure utility functions** → test directly, no render needed + +## MSW in Tests + +```typescript +import { setupServer } from "msw/node"; +import { getMyHandlers } from "~/mocks/scenarios/myFeature/myScenario"; + +const server = setupServer(...getMyHandlers()); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); +``` + +## Guidelines + +- Prefer `screen.getByRole`, `screen.getByText`, and other accessible queries over test IDs +- Use `userEvent` over `fireEvent` for realistic interaction simulation +- Test loading states, error states, and empty states — not just the happy path +- For async operations, use `waitFor` or `findBy` queries + +## Validate + +1. Run `task ui:test` and verify tests pass +2. If tests fail, fix the issues and rerun until clean diff --git a/src/uis/nvcf-ui/.coderabbit.yaml b/src/uis/nvcf-ui/.coderabbit.yaml new file mode 100644 index 000000000..acfb2d290 --- /dev/null +++ b/src/uis/nvcf-ui/.coderabbit.yaml @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: en-US +early_access: false +reviews: + profile: chill + request_changes_workflow: true + high_level_summary: true + poem: false + review_status: true + auto_review: + enabled: true + drafts: false + base_branches: + - main + pre_merge_checks: + title: + mode: error + requirements: >- + The title must follow Conventional Commits v1.0.0 (type[(scope)][!]: subject). + The scope and breaking-change ! are optional, EXCEPT that feat, fix, and perf + require a scope. Beyond format, the type must accurately reflect the primary + nature of the diff — flag mismatches (e.g. a bug fix labeled chore, a + docs-only change labeled fix). Do not flag PRs where the chosen type reflects + the most significant change across a legitimately mixed diff. + issue_assessment: + mode: warning + path_filters: + - "!vendor/kui-foundations/**" + - "!**/pnpm-lock.yaml" + - "!**/MODULE.bazel.lock" + - "!**/*.tgz" + - "!**/*.png" + - "!**/*.jpg" + path_instructions: + - path: "ui/src/features/**/*.tsx" + instructions: >- + Suspense hook variants (useGetFooSuspense) are expected when data is prefetched in + a route loader; regular useQuery is fine for secondary or deferred fetches. + Route loaders use ensureQueryData with revalidateIfStale: true for blocking data. + void prefetchQuery is intentional for fire-and-forget child-route cache warming. + No manual fetch calls for endpoints that have generated hooks in ui/src/generated/. + Prefer KUI components from @nvidia/foundations-react-core over plain HTML where a + natural fit exists — flag clear misses but use judgment. + New components should have co-located tests. + - path: "ui/src/**/*.test.tsx" + instructions: >- + Route-level views must use renderWithRouter from ~/testing/render, not standard render. + Tests should cover happy path, error state, and empty state — not just the happy path. + Prefer accessible queries (getByRole, getByText) over test IDs. + - path: "ui/src/generated/**" + instructions: >- + This is generated code (from control-plane-openapi.yaml / nvcf-openapi.yaml / sis-openapi.yaml). + Do NOT comment on code style, naming, or general best practices — Biome enforces formatting via CI. + ONLY flag spec-consistency issues: generated code that doesn't match the current spec + (e.g., missing or renamed fields, changed types, endpoints absent from codegen output), + OR generated files that appear to have been manually edited without a corresponding spec change. + - path: "spec/*.yaml" + instructions: >- + These are the source of truth for the API. Generated output lives in + ui/src/generated/ and must never be edited directly. Flag any PR that touches + ui/src/generated/ without a corresponding spec change that justifies it. + - path: "backend/**/*.go" + instructions: >- + Check %w error wrapping and structured logging with required context fields. + tools: + biome: + enabled: true + shellcheck: + enabled: true + yamllint: + enabled: true + golangci-lint: + enabled: true +chat: + auto_reply: true diff --git a/src/uis/nvcf-ui/.gitignore b/src/uis/nvcf-ui/.gitignore new file mode 100644 index 000000000..7748da64f --- /dev/null +++ b/src/uis/nvcf-ui/.gitignore @@ -0,0 +1,9 @@ +.DS_Store + +# KUI skill files — synced from node_modules on pnpm install via prepare script +.claude/skills/kaizen-ui/ + +# Bazel (MODULE.bazel.lock IS committed, like go.sum) +/bazel-* +.bazel-cache/ +user.bazelrc diff --git a/src/uis/nvcf-ui/.security-triage.yaml b/src/uis/nvcf-ui/.security-triage.yaml new file mode 100644 index 000000000..6918ee6ec --- /dev/null +++ b/src/uis/nvcf-ui/.security-triage.yaml @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# .security-triage.yaml +# Persists human/team triage decisions and confirmed classifications across runs. +# Read by security tooling (e.g. Argus) to suppress triaged findings. +version: "1.0" +project: "nvcf-ui" +last_updated: "2026-07-27" + +# No human/team classification or finding decisions were captured in this run. +# The recon-inferred classifications in SECURITY.md remain pending maintainer +# confirmation and are intentionally not persisted here. Add entries only after +# a human/team decision; see the triage-file schema for the required fields. +false_positives: [] +accepted_risks: [] +suppressed_cves: [] diff --git a/src/uis/nvcf-ui/AGENTS.md b/src/uis/nvcf-ui/AGENTS.md new file mode 100644 index 000000000..bf9a5bfc4 --- /dev/null +++ b/src/uis/nvcf-ui/AGENTS.md @@ -0,0 +1,91 @@ +# AGENTS.md + +Context and conventions for AI agents working in this codebase. Read before making changes. + +This file covers cross-cutting rules that apply to every task. Verify file paths, function names, and code patterns against the actual codebase before following them. + +## Project Structure + +``` +ui/src/ +├── main.tsx # Entry point — mounts providers, stays thin +├── rootRoute.tsx # Root route — app shell and layout +├── router.tsx # Route tree composition — imports feature routes, wires tree +├── components/ # Shared UI components (feature-agnostic) +├── features/ # One folder per domain — self-contained modules +├── generated/ # Orval output — read-only, never edit +├── hooks/ # Shared hooks (feature-agnostic) +├── lib/ # Preconfigured instances (fetch client, queryClient) +├── mocks/ # MSW setup, baseline handlers, custom scenarios +├── types/ # Shared TypeScript types +└── utils/ # Pure utility functions +``` + +## TypeScript + +- `strict: true` — do not use `any`, non-null assertions, or disable strict checks +- Path alias: `~/` maps to `src/` (configured in `tsconfig.json`) +- Use `import type` for type-only imports (`verbatimModuleSyntax: true`) + +## Import Rules + +- Features may import directly from other features, but imports must be **unidirectional** — no circular dependencies +- **Graduation rule**: graduate shared code to `src/components/`, `src/hooks/`, etc. only when the concept is genuinely domain-agnostic (e.g. `StatusBadge`, `useDebounce`). Domain-specific code stays in its owning feature +- **Component ownership**: ask "would the source feature's team recognize this as theirs?" If yes, it belongs in the source feature. If shaped by the consumer's layout, it belongs with the consumer +- Before building new components, check if similar logic or UI already exists — extract and graduate rather than duplicating + +## State Management + +- TanStack Query owns all server state — no Redux, Zustand, or global stores +- Local UI state lives in the component or a feature-scoped hook + +## Data Loading + +- Always use the generated React Query hooks — do not write manual `fetch` calls for endpoints with generated hooks +- In route components, use suspense hook variants (`useGetFunctionsSuspense`) — not `useQuery` + `isPending` checks +- Route loaders use `ensureQueryData` with `revalidateIfStale: true` to avoid flashing stale content +- Use generated query options factories (e.g. `getGetFunctionsQueryOptions`) in loaders +- Use generated Zod schemas for route search param validation via `validateSearch` + +## Fetch and Error Handling + +- `lib/fetch.ts` provides a custom fetch mutator used by all generated Orval hooks +- Non-2xx responses throw an `HttpError` automatically — do not add manual response checks in feature code +- Route errors surface through the router's global `defaultErrorComponent` (`RouteErrorFallback`), whose retry calls `router.invalidate()` to re-run the loader — don't add per-route `errorComponent` lambdas. Use `AsyncBoundary` for component-level (non-route) suspense/error isolation (e.g. dashboard panels) + +## Testing + +- Tests are not optional — when adding or modifying a component, add or update the corresponding tests +- Vitest + Testing Library; test files live alongside code (`Widget.test.tsx` next to `Widget.tsx`) +- Test user-visible behavior, not implementation details + +## Styling + +- Tailwind CSS 4 + KUI (NVIDIA design system) +- Always prefer KUI components (`Text`, `Heading`, `Panel`, `Button`, etc. from `@nvidia/foundations-react-core`) over plain HTML elements +- The KUI Tailwind plugin overrides default theme values — standard Tailwind classes already use KUI design tokens +- KUI vendor CSS is committed in `vendor/kui-foundations/` — run `task ui:vendor-css` after bumping the package + +## Backend + +- Go code lives in `backend/` — a self-contained module with its own `go.mod` + +## Linting and License + +- **UI**: Biome — `task ui:lint` / `task ui:lint:fix` +- **Backend**: golangci-lint — `task backend:lint` / `task backend:lint:fix` +- **All**: `task lint` runs everything including license check +- All source files require an Apache 2.0 license header — `task license:add` +- Run `task lint:fix` after making changes + +## Code Generation + +- Edit the spec files in `spec/` (`control-plane-openapi.yaml`, `nvcf-openapi.yaml`, `sis-openapi.yaml`) directly — never edit anything in `ui/src/generated/` +- Run `task generate` after spec changes (Orval generates React Query hooks, TypeScript model interfaces, and MSW mock handlers from each spec) +- The generated models are the source of truth — import types and status enums (e.g. `FunctionDtoStatus`, `GetClusterResponseStatus`) from `~/generated/model/...`; don't hand-roll status constants + +## Mocking (dev + tests) + +- The default MSW baseline is a **seeded in-memory store** (`ui/src/mocks/store/`): stable, referentially-consistent data (list ids match detail, keyed by the active account `ncaId`). Both the browser worker and the Node test server use it +- `VITE_SCENARIO=feature:file` files in `mocks/scenarios/` layer on top — MSW is first-match-wins and scenarios are prepended, so they override the baseline +- Fix mock-data realism at the **source** — the `mock.properties` overrides in `orval.config.ts`, then `task generate` — never by patching generated output or the store diff --git a/src/uis/nvcf-ui/BUILD.bazel b/src/uis/nvcf-ui/BUILD.bazel new file mode 100644 index 000000000..4bea41ac8 --- /dev/null +++ b/src/uis/nvcf-ui/BUILD.bazel @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@gazelle//:def.bzl", "gazelle") +load("@rules_pkg//pkg:mappings.bzl", "strip_prefix") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("//rules/oci:defs.bzl", "go_web_oci_image") + +# Gazelle manages the Go backend only; the prefix lives in backend/BUILD.bazel. +# gazelle:exclude ui +# gazelle:exclude spec +# gazelle:exclude helm +# gazelle:exclude public +# gazelle:exclude node_modules +# gazelle:exclude backend/static +# gazelle:exclude backend/bin +# gazelle:exclude backend/vendor +# gazelle:exclude .bazel-cache +# gazelle:exclude rules +gazelle(name = "gazelle") + +# Source files bundled into the container at /usr/share/nvcf-ui/src. +# Sub-packages (backend/, ui/, platforms/, rules/) are referenced via their +# own :sources filegroups because glob() does not cross package boundaries. +filegroup( + name = "source_files", + srcs = glob( + include = [ + "spec/**", + "helm/**", + "tools/**", + "*.bazel", + "*.json", + "*.yml", + "*.txt", + "*.md", + "Taskfile.yml", + "biome.json", + "LICENSE", + "NOTICE", + "THIRD-PARTY.txt", + ], + exclude = [ + "node_modules/**", + "MODULE.bazel.lock", + ], + ) + [ + "//backend:sources", + "//ui:sources", + "//platforms:sources", + "//rules/oci:sources", + ], + visibility = ["//visibility:private"], +) + +pkg_tar( + name = "source_layer", + srcs = [":source_files"], + package_dir = "/usr/share/nvcf-ui/src", + strip_prefix = strip_prefix.from_root(""), + visibility = ["//visibility:private"], +) + +# Container image (server + control-plane + built SPA on distroless). Generates +# :image, :image_index, :image_load. Matches the Dockerfile. +go_web_oci_image( + name = "image", + base = "@distroless_static", + binaries = [ + "//backend/cmd/server:server", + "//backend/cmd/control-plane:control-plane", + ], + entrypoint = ["/app/server"], + registry = "nvcr.io/0651155215864979/ncp-dev/nvcf-ui", + source_layer = "//:source_layer", + tags = ["nvcf-ui"], + visibility = ["//visibility:public"], + web_assets = "//ui", + web_dir = "/app/static", +) diff --git a/src/uis/nvcf-ui/CLAUDE.md b/src/uis/nvcf-ui/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/src/uis/nvcf-ui/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/src/uis/nvcf-ui/LICENSE b/src/uis/nvcf-ui/LICENSE new file mode 100644 index 000000000..dc8f3d683 --- /dev/null +++ b/src/uis/nvcf-ui/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/uis/nvcf-ui/MODULE.bazel b/src/uis/nvcf-ui/MODULE.bazel new file mode 100644 index 000000000..122440687 --- /dev/null +++ b/src/uis/nvcf-ui/MODULE.bazel @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""nvcf-ui Bazel module. Mirrors the nvcf/nvcf umbrella so this repo is +Bazel-buildable standalone and drops into the umbrella on migration. +""" + +module( + name = "nvcf-ui", + version = "0.0.1", +) + +# ---- OCI (multi-arch) ---- +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "rules_oci", version = "2.2.7") +bazel_dep(name = "rules_pkg", version = "1.2.0") + +# 2.22.5 (not the umbrella's 2.19.3): aspect_rules_js 3.2.2 requires >= 2.22.5. +bazel_dep(name = "aspect_bazel_lib", version = "2.22.5") +bazel_dep(name = "hermetic_cc_toolchain", version = "4.1.0") + +# ---- Go ---- +bazel_dep(name = "rules_go", version = "0.60.0") +bazel_dep(name = "gazelle", version = "0.48.0") + +# rules_pkg's pkg_tar runs through a Python builder, which pulls in +# rules_python's hermetic interpreter. CI runs as root in the bazel-ci +# container, and rules_python rejects root-user setup by default +# (https://github.com/bazelbuild/rules_python/pull/713). +bazel_dep(name = "rules_python", version = "1.4.1") + +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain( + ignore_root_user_error = True, + python_version = "3.11", +) + +go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") + +# 1.26.2 matches backend/go.mod (umbrella pins 1.25.0; reconcile on migration). +go_sdk.download(version = "1.26.2") + +go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") +go_deps.from_file(go_work = "//:go.work.bazel") + +# Managed by `bazel mod tidy`. +use_repo(go_deps, "com_github_fsnotify_fsnotify", "com_github_rs_zerolog", "in_yaml_go_yaml_v3", "io_k8s_api", "io_k8s_apimachinery", "io_k8s_client_go", "io_k8s_sigs_controller_runtime") + +# ---- Cross-compile toolchain (linux images from macOS/amd64 hosts) ---- +zig = use_extension("@hermetic_cc_toolchain//toolchain:ext.bzl", "toolchains") +use_repo(zig, "zig_sdk") + +register_toolchains( + "@zig_sdk//toolchain:linux_arm64_gnu.2.38", +) + +# ---- Base image: NVIDIA distroless-static (v4.0.0), pinned by digest ---- +oci = use_extension("@rules_oci//oci:extensions.bzl", "oci") +oci.pull( + name = "distroless_static", + digest = "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", + image = "nvcr.io/nvidia/distroless/static", + platforms = [ + "linux/amd64", + "linux/arm64/v8", + ], +) +use_repo( + oci, + "distroless_static", + "distroless_static_linux_amd64", + "distroless_static_linux_arm64_v8", +) + +# ---- JS/TS (aspect_rules_js, pnpm-native) ---- +# 3.2.x reads the build-script allowlist from pnpm-workspace.yaml (allowBuilds), +# so nothing in ui/ changes. +bazel_dep(name = "aspect_rules_js", version = "3.2.2") +bazel_dep(name = "rules_nodejs", version = "6.7.3") + +node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") +node.toolchain(node_version = "22.12.0") + +npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm") +npm.npm_translate_lock( + name = "npm", + pnpm_lock = "//ui:pnpm-lock.yaml", + verify_node_modules_ignored = "//:.bazelignore", +) +use_repo(npm, "npm") diff --git a/src/uis/nvcf-ui/MODULE.bazel.lock b/src/uis/nvcf-ui/MODULE.bazel.lock new file mode 100644 index 000000000..724e9a3f9 --- /dev/null +++ b/src/uis/nvcf-ui/MODULE.bazel.lock @@ -0,0 +1,4081 @@ +{ + "lockFileVersion": 24, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/MODULE.bazel": "004ba890363d05372a97248c37205ae64b6fa31047629cd2c0895a9d0c7779e8", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "780d1a6522b28f5edb7ea09630748720721dfe27690d65a2d33aa7509de77e07", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", + "https://bcr.bazel.build/modules/aspect_rules_js/3.2.2/MODULE.bazel": "e844196321b64537cfade338f361633e7d5fe084eee3214fc531b9e7b9838813", + "https://bcr.bazel.build/modules/aspect_rules_js/3.2.2/source.json": "0e650e493b3bc784dc3f8240ff00b4b32258cde6d021ef6b1db4e387bcfd6a9a", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/source.json": "605086bbc197743a0d360f7ddc550a1d4dfa0441bc807236e17170f636153348", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e", + "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", + "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", + "https://bcr.bazel.build/modules/bazel_features/1.41.0/source.json": "8fd525b31b0883c47e0593443cdd10219b94a7556b3195fc02d75c86c66cfe30", + "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", + "https://bcr.bazel.build/modules/bazel_lib/3.2.2/MODULE.bazel": "e2c890c8a515d6bca9c66d47718aa9e44b458fde64ec7204b8030bf2d349058c", + "https://bcr.bazel.build/modules/bazel_lib/3.2.2/source.json": "9e84e115c20e14652c5c21401ae85ff4daa8702e265b5c0b3bf89353f17aa212", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.48.0/MODULE.bazel": "ed89ed9e7e4f15a63434d553496088c5d02faa3dccc654331f3533d6542a9bb5", + "https://bcr.bazel.build/modules/gazelle/0.48.0/source.json": "30de037819207f7443d044c8873bb0b9137dd8ae8826bfb04ffe8a63e95a88dc", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.1.0/MODULE.bazel": "b512c9698968bd4548eecec8fb9bab4327c6f50fb0d12cc56e9a4d32a9a78458", + "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.1.0/source.json": "25ec6993f3acde436250883f3ab7945ff90a2fe42407af2c66f65f0f3e5b2959", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", + "https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2", + "https://bcr.bazel.build/modules/jq.bzl/0.4.0/source.json": "52ec7530c4618e03f634b30ff719814a68d7d39c235938b7aa2abbfe1eb1c52c", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", + "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", + "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", + "https://bcr.bazel.build/modules/rules_cc/0.2.16/source.json": "d03d5cde49376d87e14ec14b666c56075e5e3926930327fd5d0484a1ff2ac1cc", + "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae", + "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", + "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", + "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09", + "https://bcr.bazel.build/modules/rules_oci/2.2.7/MODULE.bazel": "f6150e4b224d459f7f6523ef65967464ca4efdd266c7fbf2f5a2a51011957e0c", + "https://bcr.bazel.build/modules/rules_oci/2.2.7/source.json": "b099f02af330f47f19dc67fc9300ef6e1937a8c86882690db0e7a2fcea8c7f6b", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e", + "https://bcr.bazel.build/modules/rules_pkg/1.2.0/source.json": "9062e00845bf91a4247465d371baa837adf9b6ff44c542f73ba084f07667e1dc", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", + "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", + "https://bcr.bazel.build/modules/rules_python/1.4.1/source.json": "8ec8c90c70ccacc4de8ca1b97f599e756fb59173e898ee08b733006650057c07", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", + "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/source.json": "20143442376c03426f6135292ba02d825cb75308aa47e6bf42dd4cc5a435c2ff", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", + "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", + "https://bcr.bazel.build/modules/yq.bzl/0.3.4/MODULE.bazel": "d3a270662f5d766cd7229732d65a5a5bc485240c3007343dd279edfb60c9ae27", + "https://bcr.bazel.build/modules/yq.bzl/0.3.4/source.json": "786dafdc2843722da3416e4343ee1a05237227f068590779a6e8496a2064c0f9", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { + "general": { + "bzlTransitiveDigest": "rWK8ttAnVBnBw1jwn8neIz9bZuiX/91Bkq/QCucF2u8=", + "usagesDigest": "v0BUZKb/gZBwBhCczerRGtre1VqU53gx1lLMtW5B+fs=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "pnpm": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", + "attributes": { + "package": "pnpm", + "version": "8.6.7", + "root_package": "", + "link_workspace": "", + "link_packages": {}, + "integrity": "sha512-vRIWpD/L4phf9Bk2o/O2TDR8fFoJnpYrp2TKqTIZF/qZ2/rgL3qKXzHofHgbXsinwMoSEigz28sqk3pQ+yMEQQ==", + "url": "", + "commit": "", + "patch_args": [ + "-p0" + ], + "patches": [], + "custom_postinstall": "", + "npm_auth": "", + "npm_auth_basic": "", + "npm_auth_username": "", + "npm_auth_password": "", + "lifecycle_hooks": [], + "extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])", + "generate_bzl_library_targets": false, + "extract_full_archive": true, + "exclude_package_contents": [], + "system_tar": "auto" + } + }, + "pnpm__links": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", + "attributes": { + "package": "pnpm", + "version": "8.6.7", + "dev": false, + "root_package": "", + "link_packages": {}, + "deps": {}, + "transitive_closure": {}, + "lifecycle_build_target": false, + "lifecycle_hooks_env": [], + "lifecycle_hooks_execution_requirements": [ + "no-sandbox" + ], + "lifecycle_hooks_use_default_shell_env": false, + "bins": {}, + "package_visibility": [ + "//visibility:public" + ], + "replace_package": "", + "exclude_package_contents": [] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "aspect_bazel_lib+", + "tar.bzl", + "tar.bzl+" + ], + [ + "aspect_rules_js+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "aspect_rules_js+", + "aspect_rules_js", + "aspect_rules_js+" + ], + [ + "aspect_rules_js+", + "bazel_features", + "bazel_features+" + ], + [ + "aspect_rules_js+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_rules_js+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_features+", + "bazel_features_globals", + "bazel_features++version_extension+bazel_features_globals" + ], + [ + "bazel_features+", + "bazel_features_version", + "bazel_features++version_extension+bazel_features_version" + ], + [ + "tar.bzl+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "tar.bzl+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "tar.bzl+", + "tar.bzl", + "tar.bzl+" + ] + ] + } + }, + "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { + "general": { + "bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=", + "usagesDigest": "qAHU62OXACzqqTdD33AhM8HizjWUgO22B+s6YCegb5I=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "aspect_tools_telemetry_report": { + "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", + "attributes": { + "deps": { + "aspect_rules_js": "3.2.2", + "aspect_tools_telemetry": "0.3.3" + } + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_tools_telemetry+", + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_tools_telemetry+", + "bazel_skylib", + "bazel_skylib+" + ] + ] + } + }, + "@@pybind11_bazel+//:python_configure.bzl%extension": { + "general": { + "bzlTransitiveDigest": "D2/qWHU6yQFwRG7Bb+caqrYMha5avsASao2vERrxK24=", + "usagesDigest": "fycyB39YnXIJkfWCIXLUKJMZzANcuLy9ZE73hRucjFk=", + "recordedFileInputs": { + "@@pybind11_bazel+//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_python": { + "repoRuleId": "@@pybind11_bazel+//:python_configure.bzl%python_configure", + "attributes": {} + }, + "pybind11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@pybind11_bazel+//:pybind11.BUILD", + "strip_prefix": "pybind11-2.11.1", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.11.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "pybind11_bazel+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { + "general": { + "bzlTransitiveDigest": "4LouzhF/yT117s7peGnNs9ROomiJXC6Zl5R0oI21jho=", + "usagesDigest": "wy6ISK6UOcBEjj/mvJ/S3WeXoO67X+1llb9yPyFtPgc=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "platforms": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz" + ], + "sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74" + } + }, + "rules_python": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8", + "strip_prefix": "rules_python-0.28.0", + "url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz" + } + }, + "bazel_skylib": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", + "urls": [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz" + ] + } + }, + "com_google_absl": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip" + ], + "strip_prefix": "abseil-cpp-20240116.1", + "integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=" + } + }, + "rules_fuzzing_oss_fuzz": { + "repoRuleId": "@@rules_fuzzing+//fuzzing/private/oss_fuzz:repository.bzl%oss_fuzz_repository", + "attributes": {} + }, + "honggfuzz": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@rules_fuzzing+//:honggfuzz.BUILD", + "sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e", + "url": "https://github.com/google/honggfuzz/archive/2.5.zip", + "strip_prefix": "honggfuzz-2.5" + } + }, + "rules_fuzzing_jazzer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", + "attributes": { + "sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2", + "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar" + } + }, + "rules_fuzzing_jazzer_api": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", + "attributes": { + "sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b", + "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_fuzzing+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "nvW/NrBXlAmiQw99EMGKkLaD2KbNp2mQDlxdfpr+0Ls=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_nodejs+//nodejs:extensions.bzl%node": { + "general": { + "bzlTransitiveDigest": "4pUxCNc22K4I+6+4Nxu52Hur12tFRfa1JMsN5mdDv60=", + "usagesDigest": "uKJ3CNzyy206RpWtEiKgCgUB2gcgUJW0m3u9oo5lczk=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "nodejs_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "nodejs_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "nodejs_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "nodejs_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "nodejs_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "nodejs_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "nodejs_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "nodejs_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "nodejs": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "nodejs" + } + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_oci+//oci:extensions.bzl%oci": { + "general": { + "bzlTransitiveDigest": "yxz4K6WSZfcCM1gDZw3dXbIYnUQfr4mOz4RPmu33NNU=", + "usagesDigest": "dtYTY4KBFDrNH3krF/vE3VC/hdKMrrtT3NGgym2X7gM=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "distroless_static_linux_amd64": { + "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_pull", + "attributes": { + "www_authenticate_challenges": {}, + "scheme": "https", + "registry": "nvcr.io", + "repository": "nvidia/distroless/static", + "identifier": "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", + "platform": "linux/amd64", + "target_name": "distroless_static_linux_amd64", + "bazel_tags": [] + } + }, + "distroless_static_linux_arm64_v8": { + "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_pull", + "attributes": { + "www_authenticate_challenges": {}, + "scheme": "https", + "registry": "nvcr.io", + "repository": "nvidia/distroless/static", + "identifier": "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", + "platform": "linux/arm64/v8", + "target_name": "distroless_static_linux_arm64_v8", + "bazel_tags": [] + } + }, + "distroless_static": { + "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_alias", + "attributes": { + "target_name": "distroless_static", + "www_authenticate_challenges": {}, + "scheme": "https", + "registry": "nvcr.io", + "repository": "nvidia/distroless/static", + "identifier": "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", + "platforms": { + "@@platforms//cpu:x86_64": "@distroless_static_linux_amd64", + "@@platforms//cpu:arm64": "@distroless_static_linux_arm64_v8" + }, + "bzlmod_repository": "distroless_static", + "reproducible": true + } + }, + "oci_crane_darwin_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "darwin_amd64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_darwin_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "darwin_arm64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_arm64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_armv6": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_armv6", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_i386": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_i386", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_s390x": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_s390x", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_amd64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_windows_armv6": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "windows_armv6", + "crane_version": "v0.18.0" + } + }, + "oci_crane_windows_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "windows_amd64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_toolchains": { + "repoRuleId": "@@rules_oci+//oci/private:toolchains_repo.bzl%toolchains_repo", + "attributes": { + "toolchain_type": "@rules_oci//oci:crane_toolchain_type", + "toolchain": "@oci_crane_{platform}//:crane_toolchain" + } + }, + "oci_regctl_darwin_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "darwin_amd64" + } + }, + "oci_regctl_darwin_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "darwin_arm64" + } + }, + "oci_regctl_linux_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "linux_arm64" + } + }, + "oci_regctl_linux_s390x": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "linux_s390x" + } + }, + "oci_regctl_linux_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "linux_amd64" + } + }, + "oci_regctl_windows_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "windows_amd64" + } + }, + "oci_regctl_toolchains": { + "repoRuleId": "@@rules_oci+//oci/private:toolchains_repo.bzl%toolchains_repo", + "attributes": { + "toolchain_type": "@rules_oci//oci:regctl_toolchain_type", + "toolchain": "@oci_regctl_{platform}//:regctl_toolchain" + } + } + }, + "moduleExtensionMetadata": { + "explicitRootModuleDirectDeps": [ + "distroless_static", + "distroless_static_linux_amd64", + "distroless_static_linux_arm64_v8" + ], + "explicitRootModuleDirectDevDeps": [], + "useAllRepos": "NO", + "reproducible": false + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_features+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_oci+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "rules_oci+", + "bazel_features", + "bazel_features+" + ], + [ + "rules_oci+", + "bazel_skylib", + "bazel_skylib+" + ] + ] + } + }, + "@@rules_python+//python/extensions:pip.bzl%pip": { + "general": { + "bzlTransitiveDigest": "C2LrqfYKhXdWDlSvRVaXkVKMOIxzPM30HuJKZEvbGko=", + "usagesDigest": "/9NP3RV6/DWuNdYAsIU/8UCgCX0TdPUJr0X6O+0lrtk=", + "recordedFileInputs": { + "@@protobuf+//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5", + "@@rules_fuzzing+//fuzzing/requirements.txt": "ab04664be026b632a0d2a2446c4f65982b7654f5b6851d2f9d399a19b7242a5b", + "@@rules_python+//tools/publish/requirements_darwin.txt": "2994136eab7e57b083c3de76faf46f70fad130bc8e7360a7fed2b288b69e79dc", + "@@rules_python+//tools/publish/requirements_linux.txt": "8175b4c8df50ae2f22d1706961884beeb54e7da27bd2447018314a175981997d", + "@@rules_python+//tools/publish/requirements_windows.txt": "7673adc71dc1a81d3661b90924d7a7c0fc998cd508b3cb4174337cef3f2de556" + }, + "recordedDirentsInputs": {}, + "envVariables": { + "RULES_PYTHON_REPO_DEBUG": null, + "RULES_PYTHON_REPO_DEBUG_VERBOSITY": null + }, + "generatedRepoSpecs": { + "pip_deps_310_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "pip_deps_310", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_310_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "pip_deps_310", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_311_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "pip_deps_311", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_311_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "pip_deps_311", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_312_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "pip_deps_312", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_312_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "pip_deps_312", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_38_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "pip_deps_38", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_38_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "pip_deps_38", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_39_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "pip_deps_39", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_39_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "pip_deps_39", + "requirement": "setuptools<=70.3.0" + } + }, + "rules_fuzzing_py_deps_310_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "rules_fuzzing_py_deps_310", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_310_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "rules_fuzzing_py_deps_310", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_311_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_fuzzing_py_deps_311", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_311_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_fuzzing_py_deps_311", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_312_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "rules_fuzzing_py_deps_312", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_312_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "rules_fuzzing_py_deps_312", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_38_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "rules_fuzzing_py_deps_38", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_38_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "rules_fuzzing_py_deps_38", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_39_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "rules_fuzzing_py_deps_39", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_39_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "rules_fuzzing_py_deps_39", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_python_publish_deps_311_backports_tarfile_py3_none_any_77e284d7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "backports.tarfile-1.2.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "backports-tarfile==1.2.0", + "sha256": "77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", + "urls": [ + "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_backports_tarfile_sdist_d75e02c2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "backports_tarfile-1.2.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "backports-tarfile==1.2.0", + "sha256": "d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", + "urls": [ + "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_certifi_py3_none_any_922820b5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "certifi-2024.8.30-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "certifi==2024.8.30", + "sha256": "922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "urls": [ + "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_certifi_sdist_bec941d2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "certifi-2024.8.30.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "certifi==2024.8.30", + "sha256": "bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", + "urls": [ + "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_a1ed2dd2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "urls": [ + "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_ppc64le_46bf4316": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "urls": [ + "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_s390x_a24ed04c": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "urls": [ + "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_x86_64_610faea7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "urls": [ + "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_aarch64_a9b15d49": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "urls": [ + "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_x86_64_fc48c783": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", + "urls": [ + "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_sdist_1c39c601": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "cffi-1.17.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "urls": [ + "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_universal2_0d99dd8f": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "urls": [ + "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_x86_64_c57516e5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "urls": [ + "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_11_0_arm64_6dba5d19": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "urls": [ + "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_aarch64_bf4475b8": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "urls": [ + "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_ppc64le_ce031db0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "urls": [ + "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_s390x_8ff4e7cd": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "urls": [ + "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_x86_64_3710a975": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "urls": [ + "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_aarch64_47334db7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "urls": [ + "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_ppc64le_f1a2f519": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "urls": [ + "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_s390x_63bc5c4a": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "urls": [ + "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_x86_64_bcb4f8ea": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "urls": [ + "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_win_amd64_cee4373f": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "urls": [ + "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_py3_none_any_fe9f97fe": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "urls": [ + "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_sdist_223217c3": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "charset_normalizer-3.4.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "urls": [ + "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_aarch64_846da004": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", + "urls": [ + "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_x86_64_0f996e72": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", + "urls": [ + "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_aarch64_f7b178f1": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", + "urls": [ + "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_x86_64_c2e6fc39": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", + "urls": [ + "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_aarch64_e1be4655": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", + "urls": [ + "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_x86_64_df6b6c6d": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", + "urls": [ + "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_sdist_315b9001": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "cryptography-43.0.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", + "urls": [ + "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_docutils_py3_none_any_dafca5b9": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "docutils-0.21.2-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "docutils==0.21.2", + "sha256": "dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", + "urls": [ + "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_docutils_sdist_3a6b1873": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "docutils-0.21.2.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "docutils==0.21.2", + "sha256": "3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", + "urls": [ + "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_idna_py3_none_any_946d195a": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "idna-3.10-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "idna==3.10", + "sha256": "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", + "urls": [ + "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_idna_sdist_12f65c9b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "idna-3.10.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "idna==3.10", + "sha256": "12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "urls": [ + "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_importlib_metadata_py3_none_any_45e54197": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "importlib_metadata-8.5.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "importlib-metadata==8.5.0", + "sha256": "45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", + "urls": [ + "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_importlib_metadata_sdist_71522656": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "importlib_metadata-8.5.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "importlib-metadata==8.5.0", + "sha256": "71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", + "urls": [ + "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jaraco_classes_py3_none_any_f662826b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "jaraco.classes-3.4.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-classes==3.4.0", + "sha256": "f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", + "urls": [ + "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jaraco_classes_sdist_47a024b5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jaraco.classes-3.4.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-classes==3.4.0", + "sha256": "47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "urls": [ + "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jaraco_context_py3_none_any_f797fc48": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "jaraco.context-6.0.1-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-context==6.0.1", + "sha256": "f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", + "urls": [ + "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jaraco_context_sdist_9bae4ea5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jaraco_context-6.0.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-context==6.0.1", + "sha256": "9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", + "urls": [ + "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jaraco_functools_py3_none_any_ad159f13": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "jaraco.functools-4.1.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-functools==4.1.0", + "sha256": "ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", + "urls": [ + "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jaraco_functools_sdist_70f7e0e2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jaraco_functools-4.1.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-functools==4.1.0", + "sha256": "70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", + "urls": [ + "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jeepney_py3_none_any_c0a454ad": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "jeepney-0.8.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jeepney==0.8.0", + "sha256": "c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755", + "urls": [ + "https://files.pythonhosted.org/packages/ae/72/2a1e2290f1ab1e06f71f3d0f1646c9e4634e70e1d37491535e19266e8dc9/jeepney-0.8.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jeepney_sdist_5efe48d2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jeepney-0.8.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jeepney==0.8.0", + "sha256": "5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", + "urls": [ + "https://files.pythonhosted.org/packages/d6/f4/154cf374c2daf2020e05c3c6a03c91348d59b23c5366e968feb198306fdf/jeepney-0.8.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_keyring_py3_none_any_5426f817": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "keyring-25.4.1-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "keyring==25.4.1", + "sha256": "5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf", + "urls": [ + "https://files.pythonhosted.org/packages/83/25/e6d59e5f0a0508d0dca8bb98c7f7fd3772fc943ac3f53d5ab18a218d32c0/keyring-25.4.1-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_keyring_sdist_b07ebc55": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "keyring-25.4.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "keyring==25.4.1", + "sha256": "b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b", + "urls": [ + "https://files.pythonhosted.org/packages/a5/1c/2bdbcfd5d59dc6274ffb175bc29aa07ecbfab196830e0cfbde7bd861a2ea/keyring-25.4.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_markdown_it_py_py3_none_any_35521684": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "markdown_it_py-3.0.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "markdown-it-py==3.0.0", + "sha256": "355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "urls": [ + "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_markdown_it_py_sdist_e3f60a94": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "markdown-it-py-3.0.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "markdown-it-py==3.0.0", + "sha256": "e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", + "urls": [ + "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_mdurl_py3_none_any_84008a41": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "mdurl-0.1.2-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "mdurl==0.1.2", + "sha256": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "urls": [ + "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_mdurl_sdist_bb413d29": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "mdurl-0.1.2.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "mdurl==0.1.2", + "sha256": "bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", + "urls": [ + "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_more_itertools_py3_none_any_037b0d32": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "more_itertools-10.5.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "more-itertools==10.5.0", + "sha256": "037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", + "urls": [ + "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_more_itertools_sdist_5482bfef": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "more-itertools-10.5.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "more-itertools==10.5.0", + "sha256": "5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", + "urls": [ + "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_14c5a72e": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86", + "urls": [ + "https://files.pythonhosted.org/packages/b3/89/1daff5d9ba5a95a157c092c7c5f39b8dd2b1ddb4559966f808d31cfb67e0/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_7b7c2a3c": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811", + "urls": [ + "https://files.pythonhosted.org/packages/2c/b6/42fc3c69cabf86b6b81e4c051a9b6e249c5ba9f8155590222c2622961f58/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_aarch64_42c64511": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200", + "urls": [ + "https://files.pythonhosted.org/packages/45/b9/833f385403abaf0023c6547389ec7a7acf141ddd9d1f21573723a6eab39a/nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_armv7l_0411beb0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164", + "urls": [ + "https://files.pythonhosted.org/packages/05/2b/85977d9e11713b5747595ee61f381bc820749daf83f07b90b6c9964cf932/nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64_5f36b271": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189", + "urls": [ + "https://files.pythonhosted.org/packages/72/f2/5c894d5265ab80a97c68ca36f25c8f6f0308abac649aaf152b74e7e854a8/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64le_34c03fa7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad", + "urls": [ + "https://files.pythonhosted.org/packages/ab/a7/375afcc710dbe2d64cfbd69e31f82f3e423d43737258af01f6a56d844085/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_s390x_19aaba96": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b", + "urls": [ + "https://files.pythonhosted.org/packages/c2/a8/3bb02d0c60a03ad3a112b76c46971e9480efa98a8946677b5a59f60130ca/nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_x86_64_de3ceed6": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307", + "urls": [ + "https://files.pythonhosted.org/packages/1b/63/6ab90d0e5225ab9780f6c9fb52254fa36b52bb7c188df9201d05b647e5e1/nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_aarch64_f0eca9ca": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe", + "urls": [ + "https://files.pythonhosted.org/packages/a3/da/0c4e282bc3cff4a0adf37005fa1fb42257673fbc1bbf7d1ff639ec3d255a/nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_armv7l_3a157ab1": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a", + "urls": [ + "https://files.pythonhosted.org/packages/de/81/c291231463d21da5f8bba82c8167a6d6893cc5419b0639801ee5d3aeb8a9/nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_x86_64_36c95d4b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204", + "urls": [ + "https://files.pythonhosted.org/packages/eb/61/73a007c74c37895fdf66e0edcd881f5eaa17a348ff02f4bb4bc906d61085/nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_win_amd64_8ce0f819": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-win_amd64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844", + "urls": [ + "https://files.pythonhosted.org/packages/26/8d/53c5b19c4999bdc6ba95f246f4ef35ca83d7d7423e5e38be43ad66544e5d/nh3-0.2.18-cp37-abi3-win_amd64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_sdist_94a16692": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "nh3-0.2.18.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4", + "urls": [ + "https://files.pythonhosted.org/packages/62/73/10df50b42ddb547a907deeb2f3c9823022580a7a47281e8eae8e003a9639/nh3-0.2.18.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pkginfo_py3_none_any_889a6da2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "pkginfo-1.10.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pkginfo==1.10.0", + "sha256": "889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097", + "urls": [ + "https://files.pythonhosted.org/packages/56/09/054aea9b7534a15ad38a363a2bd974c20646ab1582a387a95b8df1bfea1c/pkginfo-1.10.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pkginfo_sdist_5df73835": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pkginfo-1.10.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pkginfo==1.10.0", + "sha256": "5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", + "urls": [ + "https://files.pythonhosted.org/packages/2f/72/347ec5be4adc85c182ed2823d8d1c7b51e13b9a6b0c1aae59582eca652df/pkginfo-1.10.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pycparser_py3_none_any_c3702b6d": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "pycparser-2.22-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pycparser==2.22", + "sha256": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", + "urls": [ + "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pycparser_sdist_491c8be9": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pycparser-2.22.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pycparser==2.22", + "sha256": "491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "urls": [ + "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pygments_py3_none_any_b8e6aca0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "pygments-2.18.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pygments==2.18.0", + "sha256": "b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", + "urls": [ + "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pygments_sdist_786ff802": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pygments-2.18.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pygments==2.18.0", + "sha256": "786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "urls": [ + "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pywin32_ctypes_py3_none_any_8a151337": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_windows_x86_64" + ], + "filename": "pywin32_ctypes-0.2.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pywin32-ctypes==0.2.3", + "sha256": "8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", + "urls": [ + "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pywin32_ctypes_sdist_d162dc04": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pywin32-ctypes-0.2.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pywin32-ctypes==0.2.3", + "sha256": "d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", + "urls": [ + "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_readme_renderer_py3_none_any_2fbca89b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "readme_renderer-44.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "readme-renderer==44.0", + "sha256": "2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", + "urls": [ + "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_readme_renderer_sdist_8712034e": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "readme_renderer-44.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "readme-renderer==44.0", + "sha256": "8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", + "urls": [ + "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_requests_py3_none_any_70761cfe": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "requests-2.32.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests==2.32.3", + "sha256": "70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", + "urls": [ + "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_requests_sdist_55365417": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "requests-2.32.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests==2.32.3", + "sha256": "55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "urls": [ + "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_requests_toolbelt_py2_none_any_cccfdd66": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "requests_toolbelt-1.0.0-py2.py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests-toolbelt==1.0.0", + "sha256": "cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", + "urls": [ + "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_requests_toolbelt_sdist_7681a0a3": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "requests-toolbelt-1.0.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests-toolbelt==1.0.0", + "sha256": "7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "urls": [ + "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_rfc3986_py2_none_any_50b1502b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "rfc3986-2.0.0-py2.py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rfc3986==2.0.0", + "sha256": "50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "urls": [ + "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_rfc3986_sdist_97aacf9d": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "rfc3986-2.0.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rfc3986==2.0.0", + "sha256": "97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", + "urls": [ + "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_rich_py3_none_any_9836f509": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "rich-13.9.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rich==13.9.3", + "sha256": "9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283", + "urls": [ + "https://files.pythonhosted.org/packages/9a/e2/10e9819cf4a20bd8ea2f5dabafc2e6bf4a78d6a0965daeb60a4b34d1c11f/rich-13.9.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_rich_sdist_bc1e01b8": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "rich-13.9.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rich==13.9.3", + "sha256": "bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e", + "urls": [ + "https://files.pythonhosted.org/packages/d9/e9/cf9ef5245d835065e6673781dbd4b8911d352fb770d56cf0879cf11b7ee1/rich-13.9.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_secretstorage_py3_none_any_f356e662": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "SecretStorage-3.3.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "secretstorage==3.3.3", + "sha256": "f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", + "urls": [ + "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_secretstorage_sdist_2403533e": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "SecretStorage-3.3.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "secretstorage==3.3.3", + "sha256": "2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", + "urls": [ + "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_twine_py3_none_any_215dbe7b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "twine-5.1.1-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "twine==5.1.1", + "sha256": "215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", + "urls": [ + "https://files.pythonhosted.org/packages/5d/ec/00f9d5fd040ae29867355e559a94e9a8429225a0284a3f5f091a3878bfc0/twine-5.1.1-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_twine_sdist_9aa08251": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "twine-5.1.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "twine==5.1.1", + "sha256": "9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db", + "urls": [ + "https://files.pythonhosted.org/packages/77/68/bd982e5e949ef8334e6f7dcf76ae40922a8750aa2e347291ae1477a4782b/twine-5.1.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_urllib3_py3_none_any_ca899ca0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "urllib3-2.2.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "urllib3==2.2.3", + "sha256": "ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "urls": [ + "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_urllib3_sdist_e7d814a8": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "urllib3-2.2.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "urllib3==2.2.3", + "sha256": "e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", + "urls": [ + "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_zipp_py3_none_any_a817ac80": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "zipp-3.20.2-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "zipp==3.20.2", + "sha256": "a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", + "urls": [ + "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_zipp_sdist_bc9eb26f": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "zipp-3.20.2.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "zipp==3.20.2", + "sha256": "bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", + "urls": [ + "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz" + ] + } + }, + "pip_deps": { + "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", + "attributes": { + "repo_name": "pip_deps", + "extra_hub_aliases": {}, + "whl_map": { + "numpy": "{\"pip_deps_310_numpy\":[{\"version\":\"3.10\"}],\"pip_deps_311_numpy\":[{\"version\":\"3.11\"}],\"pip_deps_312_numpy\":[{\"version\":\"3.12\"}],\"pip_deps_38_numpy\":[{\"version\":\"3.8\"}],\"pip_deps_39_numpy\":[{\"version\":\"3.9\"}]}", + "setuptools": "{\"pip_deps_310_setuptools\":[{\"version\":\"3.10\"}],\"pip_deps_311_setuptools\":[{\"version\":\"3.11\"}],\"pip_deps_312_setuptools\":[{\"version\":\"3.12\"}],\"pip_deps_38_setuptools\":[{\"version\":\"3.8\"}],\"pip_deps_39_setuptools\":[{\"version\":\"3.9\"}]}" + }, + "packages": [ + "numpy", + "setuptools" + ], + "groups": {} + } + }, + "rules_fuzzing_py_deps": { + "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", + "attributes": { + "repo_name": "rules_fuzzing_py_deps", + "extra_hub_aliases": {}, + "whl_map": { + "absl_py": "{\"rules_fuzzing_py_deps_310_absl_py\":[{\"version\":\"3.10\"}],\"rules_fuzzing_py_deps_311_absl_py\":[{\"version\":\"3.11\"}],\"rules_fuzzing_py_deps_312_absl_py\":[{\"version\":\"3.12\"}],\"rules_fuzzing_py_deps_38_absl_py\":[{\"version\":\"3.8\"}],\"rules_fuzzing_py_deps_39_absl_py\":[{\"version\":\"3.9\"}]}", + "six": "{\"rules_fuzzing_py_deps_310_six\":[{\"version\":\"3.10\"}],\"rules_fuzzing_py_deps_311_six\":[{\"version\":\"3.11\"}],\"rules_fuzzing_py_deps_312_six\":[{\"version\":\"3.12\"}],\"rules_fuzzing_py_deps_38_six\":[{\"version\":\"3.8\"}],\"rules_fuzzing_py_deps_39_six\":[{\"version\":\"3.9\"}]}" + }, + "packages": [ + "absl_py", + "six" + ], + "groups": {} + } + }, + "rules_python_publish_deps": { + "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", + "attributes": { + "repo_name": "rules_python_publish_deps", + "extra_hub_aliases": {}, + "whl_map": { + "backports_tarfile": "{\"rules_python_publish_deps_311_backports_tarfile_py3_none_any_77e284d7\":[{\"filename\":\"backports.tarfile-1.2.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_backports_tarfile_sdist_d75e02c2\":[{\"filename\":\"backports_tarfile-1.2.0.tar.gz\",\"version\":\"3.11\"}]}", + "certifi": "{\"rules_python_publish_deps_311_certifi_py3_none_any_922820b5\":[{\"filename\":\"certifi-2024.8.30-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_certifi_sdist_bec941d2\":[{\"filename\":\"certifi-2024.8.30.tar.gz\",\"version\":\"3.11\"}]}", + "cffi": "{\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_a1ed2dd2\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_ppc64le_46bf4316\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_s390x_a24ed04c\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_x86_64_610faea7\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_aarch64_a9b15d49\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_x86_64_fc48c783\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_sdist_1c39c601\":[{\"filename\":\"cffi-1.17.1.tar.gz\",\"version\":\"3.11\"}]}", + "charset_normalizer": "{\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_universal2_0d99dd8f\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_x86_64_c57516e5\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_11_0_arm64_6dba5d19\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_aarch64_bf4475b8\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_ppc64le_ce031db0\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_s390x_8ff4e7cd\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_x86_64_3710a975\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_aarch64_47334db7\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_ppc64le_f1a2f519\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_s390x_63bc5c4a\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_x86_64_bcb4f8ea\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_win_amd64_cee4373f\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_py3_none_any_fe9f97fe\":[{\"filename\":\"charset_normalizer-3.4.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_sdist_223217c3\":[{\"filename\":\"charset_normalizer-3.4.0.tar.gz\",\"version\":\"3.11\"}]}", + "cryptography": "{\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_aarch64_846da004\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_x86_64_0f996e72\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_aarch64_f7b178f1\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_x86_64_c2e6fc39\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_aarch64_e1be4655\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_x86_64_df6b6c6d\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_sdist_315b9001\":[{\"filename\":\"cryptography-43.0.3.tar.gz\",\"version\":\"3.11\"}]}", + "docutils": "{\"rules_python_publish_deps_311_docutils_py3_none_any_dafca5b9\":[{\"filename\":\"docutils-0.21.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_docutils_sdist_3a6b1873\":[{\"filename\":\"docutils-0.21.2.tar.gz\",\"version\":\"3.11\"}]}", + "idna": "{\"rules_python_publish_deps_311_idna_py3_none_any_946d195a\":[{\"filename\":\"idna-3.10-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_idna_sdist_12f65c9b\":[{\"filename\":\"idna-3.10.tar.gz\",\"version\":\"3.11\"}]}", + "importlib_metadata": "{\"rules_python_publish_deps_311_importlib_metadata_py3_none_any_45e54197\":[{\"filename\":\"importlib_metadata-8.5.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_importlib_metadata_sdist_71522656\":[{\"filename\":\"importlib_metadata-8.5.0.tar.gz\",\"version\":\"3.11\"}]}", + "jaraco_classes": "{\"rules_python_publish_deps_311_jaraco_classes_py3_none_any_f662826b\":[{\"filename\":\"jaraco.classes-3.4.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_classes_sdist_47a024b5\":[{\"filename\":\"jaraco.classes-3.4.0.tar.gz\",\"version\":\"3.11\"}]}", + "jaraco_context": "{\"rules_python_publish_deps_311_jaraco_context_py3_none_any_f797fc48\":[{\"filename\":\"jaraco.context-6.0.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_context_sdist_9bae4ea5\":[{\"filename\":\"jaraco_context-6.0.1.tar.gz\",\"version\":\"3.11\"}]}", + "jaraco_functools": "{\"rules_python_publish_deps_311_jaraco_functools_py3_none_any_ad159f13\":[{\"filename\":\"jaraco.functools-4.1.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_functools_sdist_70f7e0e2\":[{\"filename\":\"jaraco_functools-4.1.0.tar.gz\",\"version\":\"3.11\"}]}", + "jeepney": "{\"rules_python_publish_deps_311_jeepney_py3_none_any_c0a454ad\":[{\"filename\":\"jeepney-0.8.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jeepney_sdist_5efe48d2\":[{\"filename\":\"jeepney-0.8.0.tar.gz\",\"version\":\"3.11\"}]}", + "keyring": "{\"rules_python_publish_deps_311_keyring_py3_none_any_5426f817\":[{\"filename\":\"keyring-25.4.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_keyring_sdist_b07ebc55\":[{\"filename\":\"keyring-25.4.1.tar.gz\",\"version\":\"3.11\"}]}", + "markdown_it_py": "{\"rules_python_publish_deps_311_markdown_it_py_py3_none_any_35521684\":[{\"filename\":\"markdown_it_py-3.0.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_markdown_it_py_sdist_e3f60a94\":[{\"filename\":\"markdown-it-py-3.0.0.tar.gz\",\"version\":\"3.11\"}]}", + "mdurl": "{\"rules_python_publish_deps_311_mdurl_py3_none_any_84008a41\":[{\"filename\":\"mdurl-0.1.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_mdurl_sdist_bb413d29\":[{\"filename\":\"mdurl-0.1.2.tar.gz\",\"version\":\"3.11\"}]}", + "more_itertools": "{\"rules_python_publish_deps_311_more_itertools_py3_none_any_037b0d32\":[{\"filename\":\"more_itertools-10.5.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_more_itertools_sdist_5482bfef\":[{\"filename\":\"more-itertools-10.5.0.tar.gz\",\"version\":\"3.11\"}]}", + "nh3": "{\"rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_14c5a72e\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_7b7c2a3c\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_aarch64_42c64511\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_armv7l_0411beb0\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64_5f36b271\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64le_34c03fa7\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_s390x_19aaba96\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_x86_64_de3ceed6\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_aarch64_f0eca9ca\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_armv7l_3a157ab1\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_x86_64_36c95d4b\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_win_amd64_8ce0f819\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-win_amd64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_sdist_94a16692\":[{\"filename\":\"nh3-0.2.18.tar.gz\",\"version\":\"3.11\"}]}", + "pkginfo": "{\"rules_python_publish_deps_311_pkginfo_py3_none_any_889a6da2\":[{\"filename\":\"pkginfo-1.10.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pkginfo_sdist_5df73835\":[{\"filename\":\"pkginfo-1.10.0.tar.gz\",\"version\":\"3.11\"}]}", + "pycparser": "{\"rules_python_publish_deps_311_pycparser_py3_none_any_c3702b6d\":[{\"filename\":\"pycparser-2.22-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pycparser_sdist_491c8be9\":[{\"filename\":\"pycparser-2.22.tar.gz\",\"version\":\"3.11\"}]}", + "pygments": "{\"rules_python_publish_deps_311_pygments_py3_none_any_b8e6aca0\":[{\"filename\":\"pygments-2.18.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pygments_sdist_786ff802\":[{\"filename\":\"pygments-2.18.0.tar.gz\",\"version\":\"3.11\"}]}", + "pywin32_ctypes": "{\"rules_python_publish_deps_311_pywin32_ctypes_py3_none_any_8a151337\":[{\"filename\":\"pywin32_ctypes-0.2.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pywin32_ctypes_sdist_d162dc04\":[{\"filename\":\"pywin32-ctypes-0.2.3.tar.gz\",\"version\":\"3.11\"}]}", + "readme_renderer": "{\"rules_python_publish_deps_311_readme_renderer_py3_none_any_2fbca89b\":[{\"filename\":\"readme_renderer-44.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_readme_renderer_sdist_8712034e\":[{\"filename\":\"readme_renderer-44.0.tar.gz\",\"version\":\"3.11\"}]}", + "requests": "{\"rules_python_publish_deps_311_requests_py3_none_any_70761cfe\":[{\"filename\":\"requests-2.32.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_requests_sdist_55365417\":[{\"filename\":\"requests-2.32.3.tar.gz\",\"version\":\"3.11\"}]}", + "requests_toolbelt": "{\"rules_python_publish_deps_311_requests_toolbelt_py2_none_any_cccfdd66\":[{\"filename\":\"requests_toolbelt-1.0.0-py2.py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_requests_toolbelt_sdist_7681a0a3\":[{\"filename\":\"requests-toolbelt-1.0.0.tar.gz\",\"version\":\"3.11\"}]}", + "rfc3986": "{\"rules_python_publish_deps_311_rfc3986_py2_none_any_50b1502b\":[{\"filename\":\"rfc3986-2.0.0-py2.py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_rfc3986_sdist_97aacf9d\":[{\"filename\":\"rfc3986-2.0.0.tar.gz\",\"version\":\"3.11\"}]}", + "rich": "{\"rules_python_publish_deps_311_rich_py3_none_any_9836f509\":[{\"filename\":\"rich-13.9.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_rich_sdist_bc1e01b8\":[{\"filename\":\"rich-13.9.3.tar.gz\",\"version\":\"3.11\"}]}", + "secretstorage": "{\"rules_python_publish_deps_311_secretstorage_py3_none_any_f356e662\":[{\"filename\":\"SecretStorage-3.3.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_secretstorage_sdist_2403533e\":[{\"filename\":\"SecretStorage-3.3.3.tar.gz\",\"version\":\"3.11\"}]}", + "twine": "{\"rules_python_publish_deps_311_twine_py3_none_any_215dbe7b\":[{\"filename\":\"twine-5.1.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_twine_sdist_9aa08251\":[{\"filename\":\"twine-5.1.1.tar.gz\",\"version\":\"3.11\"}]}", + "urllib3": "{\"rules_python_publish_deps_311_urllib3_py3_none_any_ca899ca0\":[{\"filename\":\"urllib3-2.2.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_urllib3_sdist_e7d814a8\":[{\"filename\":\"urllib3-2.2.3.tar.gz\",\"version\":\"3.11\"}]}", + "zipp": "{\"rules_python_publish_deps_311_zipp_py3_none_any_a817ac80\":[{\"filename\":\"zipp-3.20.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_zipp_sdist_bc9eb26f\":[{\"filename\":\"zipp-3.20.2.tar.gz\",\"version\":\"3.11\"}]}" + }, + "packages": [ + "backports_tarfile", + "certifi", + "charset_normalizer", + "docutils", + "idna", + "importlib_metadata", + "jaraco_classes", + "jaraco_context", + "jaraco_functools", + "keyring", + "markdown_it_py", + "mdurl", + "more_itertools", + "nh3", + "pkginfo", + "pygments", + "readme_renderer", + "requests", + "requests_toolbelt", + "rfc3986", + "rich", + "twine", + "urllib3", + "zipp" + ], + "groups": {} + } + } + }, + "recordedRepoMappingEntries": [ + [ + "bazel_features+", + "bazel_features_globals", + "bazel_features++version_extension+bazel_features_globals" + ], + [ + "bazel_features+", + "bazel_features_version", + "bazel_features++version_extension+bazel_features_version" + ], + [ + "rules_python+", + "bazel_features", + "bazel_features+" + ], + [ + "rules_python+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "rules_python+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_python+", + "pypi__build", + "rules_python++internal_deps+pypi__build" + ], + [ + "rules_python+", + "pypi__click", + "rules_python++internal_deps+pypi__click" + ], + [ + "rules_python+", + "pypi__colorama", + "rules_python++internal_deps+pypi__colorama" + ], + [ + "rules_python+", + "pypi__importlib_metadata", + "rules_python++internal_deps+pypi__importlib_metadata" + ], + [ + "rules_python+", + "pypi__installer", + "rules_python++internal_deps+pypi__installer" + ], + [ + "rules_python+", + "pypi__more_itertools", + "rules_python++internal_deps+pypi__more_itertools" + ], + [ + "rules_python+", + "pypi__packaging", + "rules_python++internal_deps+pypi__packaging" + ], + [ + "rules_python+", + "pypi__pep517", + "rules_python++internal_deps+pypi__pep517" + ], + [ + "rules_python+", + "pypi__pip", + "rules_python++internal_deps+pypi__pip" + ], + [ + "rules_python+", + "pypi__pip_tools", + "rules_python++internal_deps+pypi__pip_tools" + ], + [ + "rules_python+", + "pypi__pyproject_hooks", + "rules_python++internal_deps+pypi__pyproject_hooks" + ], + [ + "rules_python+", + "pypi__setuptools", + "rules_python++internal_deps+pypi__setuptools" + ], + [ + "rules_python+", + "pypi__tomli", + "rules_python++internal_deps+pypi__tomli" + ], + [ + "rules_python+", + "pypi__wheel", + "rules_python++internal_deps+pypi__wheel" + ], + [ + "rules_python+", + "pypi__zipp", + "rules_python++internal_deps+pypi__zipp" + ], + [ + "rules_python+", + "pythons_hub", + "rules_python++python+pythons_hub" + ], + [ + "rules_python++python+pythons_hub", + "python_3_10_host", + "rules_python++python+python_3_10_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_11_host", + "rules_python++python+python_3_11_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_12_host", + "rules_python++python+python_3_12_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_8_host", + "rules_python++python+python_3_8_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_9_host", + "rules_python++python+python_3_9_host" + ] + ] + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=", + "usagesDigest": "4JapxcpS0mL3524k0TZJffAtVyuRjDHZvN9kBRxxF1U=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python+", + "platforms", + "platforms" + ] + ] + } + }, + "@@yq.bzl+//yq:extensions.bzl%yq": { + "general": { + "bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=", + "usagesDigest": "z7g7IZt6aaEeYztkX2Hs5qlSifRJcDTGz9K/CD4yOFI=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "yq_darwin_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "darwin_amd64", + "version": "4.45.2" + } + }, + "yq_darwin_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "darwin_arm64", + "version": "4.45.2" + } + }, + "yq_linux_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_amd64", + "version": "4.45.2" + } + }, + "yq_linux_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_arm64", + "version": "4.45.2" + } + }, + "yq_linux_s390x": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_s390x", + "version": "4.45.2" + } + }, + "yq_linux_riscv64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_riscv64", + "version": "4.45.2" + } + }, + "yq_linux_ppc64le": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_ppc64le", + "version": "4.45.2" + } + }, + "yq_windows_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "windows_amd64", + "version": "4.45.2" + } + }, + "yq_windows_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "windows_arm64", + "version": "4.45.2" + } + }, + "yq_toolchains": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:toolchain.bzl%yq_toolchains_repo", + "attributes": { + "user_repository_name": "yq" + } + } + }, + "recordedRepoMappingEntries": [] + } + } + }, + "facts": { + "@@rules_go+//go:extensions.bzl%go_sdk": { + "1.25.0": { + "aix_ppc64": [ + "go1.25.0.aix-ppc64.tar.gz", + "e5234a7dac67bc86c528fe9752fc9d63557918627707a733ab4cac1a6faed2d4" + ], + "darwin_amd64": [ + "go1.25.0.darwin-amd64.tar.gz", + "5bd60e823037062c2307c71e8111809865116714d6f6b410597cf5075dfd80ef" + ], + "darwin_arm64": [ + "go1.25.0.darwin-arm64.tar.gz", + "544932844156d8172f7a28f77f2ac9c15a23046698b6243f633b0a0b00c0749c" + ], + "dragonfly_amd64": [ + "go1.25.0.dragonfly-amd64.tar.gz", + "5ed3cf9a810a1483822538674f1336c06b51aa1b94d6d545a1a0319a48177120" + ], + "freebsd_386": [ + "go1.25.0.freebsd-386.tar.gz", + "abea5d5c6697e6b5c224731f2158fe87c602996a2a233ac0c4730cd57bf8374e" + ], + "freebsd_amd64": [ + "go1.25.0.freebsd-amd64.tar.gz", + "86e6fe0a29698d7601c4442052dac48bd58d532c51cccb8f1917df648138730b" + ], + "freebsd_arm": [ + "go1.25.0.freebsd-arm.tar.gz", + "d90b78e41921f72f30e8bbc81d9dec2cff7ff384a33d8d8debb24053e4336bfe" + ], + "freebsd_arm64": [ + "go1.25.0.freebsd-arm64.tar.gz", + "451d0da1affd886bfb291b7c63a6018527b269505db21ce6e14724f22ab0662e" + ], + "freebsd_riscv64": [ + "go1.25.0.freebsd-riscv64.tar.gz", + "7b565f76bd8bda46549eeaaefe0e53b251e644c230577290c0f66b1ecdb3cdbe" + ], + "illumos_amd64": [ + "go1.25.0.illumos-amd64.tar.gz", + "b1e1fdaab1ad25aa1c08d7a36c97d45d74b98b89c3f78c6d2145f77face54a2c" + ], + "linux_386": [ + "go1.25.0.linux-386.tar.gz", + "8c602dd9d99bc9453b3995d20ce4baf382cc50855900a0ece5de9929df4a993a" + ], + "linux_amd64": [ + "go1.25.0.linux-amd64.tar.gz", + "2852af0cb20a13139b3448992e69b868e50ed0f8a1e5940ee1de9e19a123b613" + ], + "linux_arm64": [ + "go1.25.0.linux-arm64.tar.gz", + "05de75d6994a2783699815ee553bd5a9327d8b79991de36e38b66862782f54ae" + ], + "linux_armv6l": [ + "go1.25.0.linux-armv6l.tar.gz", + "a5a8f8198fcf00e1e485b8ecef9ee020778bf32a408a4e8873371bfce458cd09" + ], + "linux_loong64": [ + "go1.25.0.linux-loong64.tar.gz", + "cab86b1cf761b1cb3bac86a8877cfc92e7b036fc0d3084123d77013d61432afc" + ], + "linux_mips": [ + "go1.25.0.linux-mips.tar.gz", + "d66b6fb74c3d91b9829dc95ec10ca1f047ef5e89332152f92e136cf0e2da5be1" + ], + "linux_mips64": [ + "go1.25.0.linux-mips64.tar.gz", + "4082e4381a8661bc2a839ff94ba3daf4f6cde20f8fb771b5b3d4762dc84198a2" + ], + "linux_mips64le": [ + "go1.25.0.linux-mips64le.tar.gz", + "70002c299ec7f7175ac2ef673b1b347eecfa54ae11f34416a6053c17f855afcc" + ], + "linux_mipsle": [ + "go1.25.0.linux-mipsle.tar.gz", + "b00a3a39eff099f6df9f1c7355bf28e4589d0586f42d7d4a394efb763d145a73" + ], + "linux_ppc64": [ + "go1.25.0.linux-ppc64.tar.gz", + "df166f33bd98160662560a72ff0b4ba731f969a80f088922bddcf566a88c1ec1" + ], + "linux_ppc64le": [ + "go1.25.0.linux-ppc64le.tar.gz", + "0f18a89e7576cf2c5fa0b487a1635d9bcbf843df5f110e9982c64df52a983ad0" + ], + "linux_riscv64": [ + "go1.25.0.linux-riscv64.tar.gz", + "c018ff74a2c48d55c8ca9b07c8e24163558ffec8bea08b326d6336905d956b67" + ], + "linux_s390x": [ + "go1.25.0.linux-s390x.tar.gz", + "34e5a2e19f2292fbaf8783e3a241e6e49689276aef6510a8060ea5ef54eee408" + ], + "netbsd_386": [ + "go1.25.0.netbsd-386.tar.gz", + "f8586cdb7aa855657609a5c5f6dbf523efa00c2bbd7c76d3936bec80aa6c0aba" + ], + "netbsd_amd64": [ + "go1.25.0.netbsd-amd64.tar.gz", + "ae8dc1469385b86a157a423bb56304ba45730de8a897615874f57dd096db2c2a" + ], + "netbsd_arm": [ + "go1.25.0.netbsd-arm.tar.gz", + "1ff7e4cc764425fc9dd6825eaee79d02b3c7cafffbb3691687c8d672ade76cb7" + ], + "netbsd_arm64": [ + "go1.25.0.netbsd-arm64.tar.gz", + "e1b310739f26724216aa6d7d7208c4031f9ff54c9b5b9a796ddc8bebcb4a5f16" + ], + "openbsd_386": [ + "go1.25.0.openbsd-386.tar.gz", + "4802a9b20e533da91adb84aab42e94aa56cfe3e5475d0550bed3385b182e69d8" + ], + "openbsd_amd64": [ + "go1.25.0.openbsd-amd64.tar.gz", + "c016cd984bebe317b19a4f297c4f50def120dc9788490540c89f28e42f1dabe1" + ], + "openbsd_arm": [ + "go1.25.0.openbsd-arm.tar.gz", + "a1e31d0bf22172ddde42edf5ec811ef81be43433df0948ece52fecb247ccfd8d" + ], + "openbsd_arm64": [ + "go1.25.0.openbsd-arm64.tar.gz", + "343ea8edd8c218196e15a859c6072d0dd3246fbbb168481ab665eb4c4140458d" + ], + "openbsd_ppc64": [ + "go1.25.0.openbsd-ppc64.tar.gz", + "694c14da1bcaeb5e3332d49bdc2b6d155067648f8fe1540c5de8f3cf8e157154" + ], + "openbsd_riscv64": [ + "go1.25.0.openbsd-riscv64.tar.gz", + "aa510ad25cf54c06cd9c70b6d80ded69cb20188ac6e1735655eef29ff7e7885f" + ], + "plan9_386": [ + "go1.25.0.plan9-386.tar.gz", + "46f8cef02086cf04bf186c5912776b56535178d4cb319cd19c9fdbdd29231986" + ], + "plan9_amd64": [ + "go1.25.0.plan9-amd64.tar.gz", + "29b34391d84095e44608a228f63f2f88113a37b74a79781353ec043dfbcb427b" + ], + "plan9_arm": [ + "go1.25.0.plan9-arm.tar.gz", + "0a047107d13ebe7943aaa6d54b1d7bbd2e45e68ce449b52915a818da715799c2" + ], + "solaris_amd64": [ + "go1.25.0.solaris-amd64.tar.gz", + "9977f9e4351984364a3b2b78f8b88bfd1d339812356d5237678514594b7d3611" + ], + "windows_386": [ + "go1.25.0.windows-386.zip", + "df9f39db82a803af0db639e3613a36681ab7a42866b1384b3f3a1045663961a7" + ], + "windows_amd64": [ + "go1.25.0.windows-amd64.zip", + "89efb4f9b30812eee083cc1770fdd2913c14d301064f6454851428f9707d190b" + ], + "windows_arm64": [ + "go1.25.0.windows-arm64.zip", + "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" + ] + }, + "1.26.2": { + "aix_ppc64": [ + "go1.26.2.aix-ppc64.tar.gz", + "e6f759fbdd5b1e3ecce3161d8bd9b9aeaf15c4112b7c101097e9d329b310d858" + ], + "darwin_amd64": [ + "go1.26.2.darwin-amd64.tar.gz", + "bc3f1500d9968c36d705442d90ba91addf9271665033748b82532682e90a7966" + ], + "darwin_arm64": [ + "go1.26.2.darwin-arm64.tar.gz", + "32af1522bf3e3ff3975864780a429cc0b41d190ec7bf90faa661d6d64566e7af" + ], + "dragonfly_amd64": [ + "go1.26.2.dragonfly-amd64.tar.gz", + "b4f3f74412914e54140cbf8b5c76d2f8eeff3a5003310c9244c61ba8a0ecd94b" + ], + "freebsd_386": [ + "go1.26.2.freebsd-386.tar.gz", + "fab09ea1988aae3ae2c8186455e8956d539d04e2ee4973e8887853432d0e8039" + ], + "freebsd_amd64": [ + "go1.26.2.freebsd-amd64.tar.gz", + "f271fd829a2a6b36fa1c72cdaafb18410a106da982c93a626d4e8b0fa0f0fa21" + ], + "freebsd_arm": [ + "go1.26.2.freebsd-arm.tar.gz", + "38713f1516cd2b0097ea05594ec1c842fe690dace8301c7d34d91b92250b4424" + ], + "freebsd_arm64": [ + "go1.26.2.freebsd-arm64.tar.gz", + "d78bb171900134efdd1d0d49e5e80cd8c8b614f0e46c508d0b6bac30fb996fdf" + ], + "illumos_amd64": [ + "go1.26.2.illumos-amd64.tar.gz", + "e88cd85e9e253ceda4077367072aa10d2f92bc2fa6e59a811c00bbe95dc9b02d" + ], + "linux_386": [ + "go1.26.2.linux-386.tar.gz", + "89835cdc4dfebde7fe28c9c6dc080bb3753f6b0354301966ff9f62d14991bd7d" + ], + "linux_amd64": [ + "go1.26.2.linux-amd64.tar.gz", + "990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282" + ], + "linux_arm64": [ + "go1.26.2.linux-arm64.tar.gz", + "c958a1fe1b361391db163a485e21f5f228142d6f8b584f6bef89b26f66dc5b23" + ], + "linux_armv6l": [ + "go1.26.2.linux-armv6l.tar.gz", + "0000e45577827b0a8868588c543cbe4232853def1d3d7a344ad6e60ce2b015c8" + ], + "linux_loong64": [ + "go1.26.2.linux-loong64.tar.gz", + "4dcb87e845fe5c015c8cf6affb4636fcf1699182b70454783caed85c5dfa3267" + ], + "linux_mips": [ + "go1.26.2.linux-mips.tar.gz", + "2d57e4167932a4872e31570465f48d8b6818002c77275bae969bdbb6d7238b5e" + ], + "linux_mips64": [ + "go1.26.2.linux-mips64.tar.gz", + "b0b49bb5c528e623a926b8242f03cfd612971150e18eeb3f638d751218c09cdf" + ], + "linux_mips64le": [ + "go1.26.2.linux-mips64le.tar.gz", + "5ffc9da2b0ee939c8503c9d9278d6857909ca8577dae3b71221ab2821dc7826a" + ], + "linux_mipsle": [ + "go1.26.2.linux-mipsle.tar.gz", + "ab1fe1c38ffa6bbda029dea33bf36167aca4ff3a25c9d6ed0af38da120678ddc" + ], + "linux_ppc64": [ + "go1.26.2.linux-ppc64.tar.gz", + "589f7ef241104f153e910244b71d70f4aad0d4584651ca80a5188186dda63a2e" + ], + "linux_ppc64le": [ + "go1.26.2.linux-ppc64le.tar.gz", + "62b7645dd2404052535617c59e91cf03c7aa28e332dbaddbe4c0d7de7bcc6736" + ], + "linux_riscv64": [ + "go1.26.2.linux-riscv64.tar.gz", + "c5c697faa4dc05364b6e163d2ab8161b32a120eeed54192457d57d7ef7c2091a" + ], + "linux_s390x": [ + "go1.26.2.linux-s390x.tar.gz", + "410726ed10a0ea6745c2ea8da4f0e769fd3ce819cd4a41a67ad08b094d5dfc31" + ], + "netbsd_386": [ + "go1.26.2.netbsd-386.tar.gz", + "e8ffab99dd65fef14097d6af48ea6302793f2298a7b2e5f00a284bd933feba4c" + ], + "netbsd_amd64": [ + "go1.26.2.netbsd-amd64.tar.gz", + "1f5c33c923983ee8433ad8098dcd87a0e1fdccd18d05a91844c2be60507a61fe" + ], + "netbsd_arm": [ + "go1.26.2.netbsd-arm.tar.gz", + "85761320e364b65424d2952a3388970d86e072c8dce8dcad2a1dca41555c2b96" + ], + "netbsd_arm64": [ + "go1.26.2.netbsd-arm64.tar.gz", + "3ca3561bf4452e799d11e5312182f79cd342d506136cd44c2b47991206ec23f5" + ], + "openbsd_386": [ + "go1.26.2.openbsd-386.tar.gz", + "0644073c0ae1ade26d26953ef882d7d419855dca25b0e992ae416a47967d37b3" + ], + "openbsd_amd64": [ + "go1.26.2.openbsd-amd64.tar.gz", + "72f69217a88e3d0975a75adf9fc92ff10ea65def56e6c34d8612428bf769581e" + ], + "openbsd_arm": [ + "go1.26.2.openbsd-arm.tar.gz", + "3aafe792df65abf1777b5cc678075ebba1bd8063e6450e81e742fef696e0bcbd" + ], + "openbsd_arm64": [ + "go1.26.2.openbsd-arm64.tar.gz", + "efd410fc60a17690ad43fe8dd00bf1fd4c1dc920d81970b9f422f313b0930b92" + ], + "openbsd_ppc64": [ + "go1.26.2.openbsd-ppc64.tar.gz", + "98a2cadd416066739e00b1bc297727c3108dba9001811b177ce57afef518f8bd" + ], + "openbsd_riscv64": [ + "go1.26.2.openbsd-riscv64.tar.gz", + "adf39a1a7e56d5c1a2cb69ad06752c5da6b808d2a029b7b6d6d5bb6e11a2168e" + ], + "plan9_386": [ + "go1.26.2.plan9-386.tar.gz", + "eb2f95f6f43701eb98a31f5efdd9b5f14ff6e0afd289e1f94559462f83e73cfd" + ], + "plan9_amd64": [ + "go1.26.2.plan9-amd64.tar.gz", + "d347fecec7532309fccf3570021ba8a00a0acce120fea02a46cc295936588b0f" + ], + "plan9_arm": [ + "go1.26.2.plan9-arm.tar.gz", + "70700c5af45201a8e82436fb824f3551e357e3002094c8416e78e1aa51d4a0ab" + ], + "solaris_amd64": [ + "go1.26.2.solaris-amd64.tar.gz", + "cd45d13200697b3263e39cdf364f0cb9d9adc39d9574ab575e481b64cb7fb8b1" + ], + "windows_386": [ + "go1.26.2.windows-386.zip", + "4a8b02c34625fecd9c6583442101c9796fc265a5fc1edb9340d71bed0300f94c" + ], + "windows_amd64": [ + "go1.26.2.windows-amd64.zip", + "98eb3570bade15cb826b0909338df6cc6d2cf590bc39c471142002db3832b708" + ], + "windows_arm64": [ + "go1.26.2.windows-arm64.zip", + "094d05caaf6ba235e2bd570b625d064ceb65943866252722a8f3fdba232139c6" + ] + } + } + } +} diff --git a/src/uis/nvcf-ui/NOTICE b/src/uis/nvcf-ui/NOTICE new file mode 100644 index 000000000..438648e2e --- /dev/null +++ b/src/uis/nvcf-ui/NOTICE @@ -0,0 +1,14 @@ +NVCF UI +Copyright 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/uis/nvcf-ui/README.md b/src/uis/nvcf-ui/README.md new file mode 100644 index 000000000..a3c7511df --- /dev/null +++ b/src/uis/nvcf-ui/README.md @@ -0,0 +1,126 @@ +# NVIDIA Cloud Functions UI + +A web interface for managing NVIDIA Cloud Functions (NVCF), built as a single-page application (SPA). + +## Prerequisites + +- **Go** 1.24+ +- **Node** 22+ +- **pnpm** 10+ +- **[Task]** 3+ + +[Task]: https://taskfile.dev + +## Quick Start + +```sh +task setup # install Go tooling and UI dependencies +task ui:dev MOCK=true # start Vite dev server on :5173 with mocked APIs +``` + +## Build with Bazel + +Bazel is the canonical build path. The `task` commands below stay available for +local iteration outside CI. + +Requires [bazelisk](https://github.com/bazelbuild/bazelisk) (`bazel` on PATH +delegating to the version pinned in `.bazelversion`). + +```bash +# Build every Bazel target. +bazel build //... + +# Run all tests. +bazel test //... --flaky_test_attempts=3 + +# Build the multi-arch OCI image index (linux/amd64 + linux/arm64). +bazel build //:image_index + +# Load the host-arch image into the local docker daemon. +bazel run //:image_load + +# Refresh backend/BUILD.bazel files after Go source changes. +bazel run //:gazelle +``` + +Image publishing to the staging registry is performed by the CI pipeline, not +a local Bazel push target. + +> The `//ui` target carries `tags = ["no-sandbox"]` — required for Vite 8's Rolldown +> bundler under Bazel ([rules_js#2888](https://github.com/aspect-build/rules_js/issues/2888)). +> Do not remove it. + +## Project Structure + +``` +├── backend/ # Go BFF — proxy, control-plane, static SPA serving +├── ui/ # React SPA — Vite, TanStack Router/Query, Tailwind, KUI +├── spec/ # OpenAPI specification (source of truth for codegen) +├── helm/ # Helm chart for Kubernetes deployment +├── rules/ # Bazel macros (OCI image packaging) +├── platforms/ # Bazel platform definitions (linux amd64/arm64) +├── MODULE.bazel # Bazel (bzlmod) module — Go, UI, and image build +├── Taskfile.yml # Root task runner (orchestrates ui/ and backend/ tasks) +└── .gitlab-ci.yml # CI pipeline — lint, typecheck, test, deploy +``` + +## Development + +### Commands + +| Command | Description | +| ---------------------------------- | -------------------------------------------- | +| `task setup` | Bootstrap a fresh clone | +| `task ui:dev` | Start Vite dev server | +| `task ui:dev MOCK=true` | Start with mock API responses (no live API) | +| `task generate` | Regenerate API hooks and mocks from spec | +| `task typecheck` | Type-check the UI | +| `task build` | Production build (UI + Go binaries) | +| `task lint` | Run all linters and license check | +| `task lint:fix` | Auto-fix lint and formatting issues | +| `task test` | Run all tests | +| `task ui:vendor-css` | Fetch KUI CSS (after bumping foundations) | + +### Dev Modes + +**Mock mode** — Run the UI standalone with mocked API responses. No live API or cluster required. + +```sh +task ui:dev MOCK=true +``` + +By default, mock responses come from a seeded in-memory store built on the OpenAPI schemas (via Orval), so data is stable and referentially consistent — list rows match their detail pages and everything is keyed by the active account. To test specific UI states (empty lists, error conditions, etc.), use **scenarios** — named overrides layered on top of the default handlers: + +```sh +task ui:dev MOCK=true SCENARIO=functions:empty-list +``` + +Multiple scenarios can be combined with commas: `SCENARIO=functions:empty-list,tasks:launching-task`. Scenarios live in `ui/src/mocks/scenarios//.ts`. + +**Live API mode** — The backend-for-frontend server lives in `backend/` and is compiled by `task build`. Running it against live upstreams requires in-cluster access and mounted secrets, so for day-to-day UI work use mock mode. + +### Code Generation + +API client hooks, TypeScript models, and MSW mock handlers are generated from the OpenAPI specs via Orval. After modifying the specs in `spec/` (`control-plane-openapi.yaml`, `nvcf-openapi.yaml`, `sis-openapi.yaml`), run: + +```sh +task generate +``` + +Generated code lives in `ui/src/generated/` and should not be edited by hand. + +### Testing + +Tests use [Vitest](https://vitest.dev/) with [Testing Library](https://testing-library.com/). Test files live alongside the code they test (e.g. `FunctionsList.test.tsx` next to `FunctionsList.tsx`). + +```sh +task test +``` + +## Architecture + +The application follows a **backend-for-frontend (BFF)** pattern: the React SPA never talks to upstream services directly. A BFF handles authentication, proxies API requests to upstream NVCF services, and serves the built SPA as static files. That server lives in `backend/`. + +## License + +[Apache 2.0](LICENSE) diff --git a/src/uis/nvcf-ui/SECURITY.md b/src/uis/nvcf-ui/SECURITY.md new file mode 100644 index 000000000..90bf4d91c --- /dev/null +++ b/src/uis/nvcf-ui/SECURITY.md @@ -0,0 +1,195 @@ +# Security Policy: NVIDIA Cloud Functions UI (nvcf-ui) + +NVIDIA is dedicated to the security and trust of our software products and +services, including all source code repositories managed through our +organization. + +If you need to report a security issue, please use the appropriate contact +points outlined below. **Please do not report security vulnerabilities through +GitLab issues, merge requests, or public discussions.** + +## Reporting a Vulnerability + +If you discover a potential security vulnerability in this project, please +report it through one of the following channels. The first two are the +guaranteed NVIDIA disclosure channels and should be preferred: + +* **Web (preferred):** [NVIDIA Vulnerability Disclosure Program](https://www.nvidia.com/en-us/security/) + — the preferred method for reporting security concerns across all NVIDIA + products. +* **E-Mail:** [psirt@nvidia.com](mailto:psirt@nvidia.com) + — We encourage you to use the following PGP key for secure email + communication: [NVIDIA public PGP Key](https://www.nvidia.com/en-us/security/pgp-key) +* **Repository private reporting (optional fallback, if available on this host):** + if this repository's host has private vulnerability reporting enabled, you may + use the repository **Security** tab > **Report a vulnerability**. Not all + GitLab/GitHub hosts enable this; when it is unavailable, use the web portal or + PSIRT email above. + +If a security vulnerability is reported through public channels (issues, merge +requests, or discussions), maintainers may limit public discussion and redirect +the reporter to the private disclosure channels above. + +### What to Include in Your Report + +Detailed reports help NVIDIA evaluate and address issues faster. Please include: + +- Product/project name and version or branch affected +- Type of vulnerability (e.g., authentication bypass, injection, information + disclosure, privilege escalation) +- Step-by-step instructions to reproduce the issue +- Proof-of-concept code or exploit (if available) +- Potential impact assessment + +### What to Expect + +NVIDIA's Product Security Incident Response Team (PSIRT) will: +1. Acknowledge receipt of your report +2. Validate the vulnerability and assess severity +3. Develop and test a fix +4. Publish a security bulletin as appropriate + +For ongoing security updates, subscribe to notifications at the +[NVIDIA Product Security](https://www.nvidia.com/en-us/security/) portal. + +## Security Architecture & Context + +`nvcf-ui` is the management web interface for **NVIDIA Cloud Functions (NVCF)**. +It is composed of two parts that ship together: + +- A **React single-page application (SPA)** (`ui/`) built with Vite, TanStack + Router/Query, Tailwind, and the KUI design system. It renders the management + console and calls same-origin API paths. +- A **Go backend-for-frontend (BFF)** (`backend/cmd/server`) that serves the + built SPA as static files and reverse-proxies the SPA's API calls to upstream + NVCF control-plane services. The SPA never talks to upstream services + directly. + +A second, headless **control-plane health monitor** binary +(`backend/cmd/control-plane`) runs as its own Deployment. It periodically probes +the health of NVCF platform components and writes the results to a Kubernetes +ConfigMap; the BFF reads that ConfigMap to serve `GET /v1/control-plane`. + +This software operates at the **Service (backend-for-frontend) / Application +(SPA)** level. Its primary security responsibility is to **broker read-only +access from the browser to upstream NVCF control-plane APIs by attaching +service credentials on behalf of the caller**, and to serve the SPA. It does not +store user data itself; its most sensitive asset is the set of upstream service +bearer tokens it holds in memory. + +**Repository Exposure Classification (inferred; pending maintainer confirmation):** +Internal. +Basis: origin remote is an internal, self-managed NVIDIA GitLab host. + +**Service Exposure Classification (inferred; pending maintainer confirmation):** +External / Regulated (medium confidence). +Basis: management console for the commercial, customer-facing NVIDIA Cloud +Functions product; runs as a production Kubernetes service, brokers access to +NVCF, NVCT, and cluster control-plane APIs, holds upstream service credentials, and is +packaged and published to the NGC registry with nSpect registration. The BFF's +own external reachability depends on the deploying cluster's gateway +configuration (the Gateway API `HTTPRoute` is off by default in the chart), +which is why confidence is medium rather than high. + +### Key Security Boundaries + +- **Browser ↔ BFF (untrusted → semi-trusted):** The BFF accepts HTTP requests + from browsers. It enforces a **read-only surface**: a method-allowlist + middleware (`middleware.AllowReadMethods`) rejects any method other than + `GET`/`HEAD` with `405` before the request reaches any handler, and the static + file handler likewise serves only `GET`/`HEAD`. +- **BFF ↔ upstream APIs (credential injection boundary):** For each proxied + request the BFF sets `Authorization: Bearer `, where the token is the + service token for that upstream (NVCF, NVCT, or cluster). The BFF does **not** + itself authenticate the browser caller; it relies on an upstream + gateway/ingress to authenticate and authorize end users before requests reach + it. Requests are proxied path-unchanged (`/v2/nvcf/…`, `/v1/nvct/…`, + `/v1/si/…`) to fixed in-cluster upstream addresses supplied by configuration. +- **Token source (secret boundary):** Upstream tokens are read from a tokens + file rendered by an OpenBao/Vault agent sidecar into an in-pod secret volume. + A file watcher reloads them atomically on rotation and marks a token invalid + shortly before its JWT `exp`; if a token is missing/invalid the BFF fails the + request with `500` rather than forwarding an unauthenticated call. +- **Kubernetes control plane (least privilege):** The BFF's ServiceAccount has a + **namespaced, read-only** Role (`get`/`list`/`watch` on ConfigMaps in its own + namespace only) used to read the control-plane health ConfigMap. Containers + run as non-root (UID/GID 65532) from a distroless image with all Linux + capabilities dropped. + +### Threat Model + +The following scenarios represent the primary security concerns for this project +(including auxiliary/support code such as the health monitor and audit logging): + +1. **Upstream credential exposure via the BFF:** The BFF holds NVCF, NVCT, and + cluster bearer tokens in memory and injects them into every proxied request. A + flaw that reflects request/response internals, a proxy misconfiguration, or a + log statement that captured the `Authorization` header could leak a + privileged upstream service token. Compromise of the tokens file or its + OpenBao/Vault agent mount would expose all three tokens at once. +2. **Missing caller authentication → confused-deputy access:** The BFF does not + authenticate browser callers; it attaches its own service token to every + request. If the deployment exposes the BFF without an authenticating + gateway/ingress in front of it, any party that can reach the BFF can drive + upstream NVCF, NVCT, and cluster read APIs with the service's credentials. +3. **Server-side request forwarding to unintended upstreams:** The reverse proxy + forwards requests path-unchanged to upstream hosts taken from the + `NVCF_URL` / `NVCT_URL` / `SIS_URL` configuration. If those values are + attacker-influenced (e.g., a tampered ConfigMap/env), requests carrying the + injected bearer token could be directed to an unintended host. +4. **Internal topology disclosure via control-plane health:** `GET + /v1/control-plane` returns component names, namespaces, and health status + sourced from the health-monitor ConfigMap. Without an authenticating layer in + front, this reveals internal platform topology that aids reconnaissance. +5. **SPA deep-link / static-serving abuse:** The BFF maps unknown paths to + `index.html` to support client-side routing. Errors in static-file indexing + or cache-control handling could serve stale/incorrect assets; the immutable + long-cache policy on `assets/` assumes content-hashed filenames. +6. **Supply-chain / build integrity:** The SPA and Go binaries pull dependencies + (pnpm/npm and Go modules) and are packaged into an OCI image and Helm chart + published to the NGC registry. A compromised dependency, build step, or CI + credential (`GL_TOKEN`, NGC push key, publish trigger token) could inject + malicious code into a released artifact. +7. **Denial of service against the proxy / monitor:** Unauthenticated, + high-volume GET traffic to the BFF (or to the always-running control-plane + monitor's upstream probes) could exhaust resources, since the BFF applies no + built-in rate limiting and relies on upstream/gateway controls. + +### Critical Security Assumptions + +The following are delegated to other layers; this software does **not** protect +against them on its own: + +- **Assumes callers are authenticated upstream.** The BFF performs no user + authentication or authorization of its own. It assumes an authenticating, + authorizing gateway/ingress sits in front of it in any exposed deployment, and + that end-user identity/authorization is enforced there (and/or by the upstream + NVCF, NVCT, and cluster services). +- **Assumes the OpenBao/Vault token source is trusted.** The BFF treats the + tokens file and the injected upstream JWTs as authoritative. It decodes the + JWT payload only to read the `exp` claim for rotation timing; it does **not** + verify token signatures. Token confidentiality, validity, and issuance are the + responsibility of the OpenBao/Vault infrastructure and the secret mount. +- **Assumes trusted, network-isolated upstreams.** Requests are proxied over + plaintext `http://` to in-cluster service addresses. Transport confidentiality + between the BFF and upstreams is assumed to be provided by the cluster network + / service mesh, and the configured upstream addresses are assumed to be + correct and immutable by attackers. +- **Assumes the deployment/orchestration layer is trusted.** Configuration + (`NVCF_URL`, `NVCT_URL`, `SIS_URL`, tokens path), the Helm chart values, RBAC + bindings, and ConfigMaps are assumed to be provisioned and protected by the + cluster operators. The control-plane monitor trusts the components list + mounted from its ConfigMap. +- **Assumes the container/host platform enforces isolation.** The image runs as + non-root with all capabilities dropped and relies on Kubernetes and the host + OS to enforce process, filesystem, and network isolation. +- **Assumes build and release infrastructure is trusted.** Dependency + resolution, the Bazel/CI build, image signing/publishing to the NGC registry, + and the CI secrets used to do so are assumed to be operated securely. + +## Security Update Process + +Security fixes follow the standard NVIDIA PSIRT coordinated-disclosure process. +Releases are cut via the project's semantic-release pipeline and published as +versioned OCI images and Helm charts to the NGC registry. Consumers should track +released versions and apply security-relevant updates promptly. diff --git a/src/uis/nvcf-ui/THIRD-PARTY.txt b/src/uis/nvcf-ui/THIRD-PARTY.txt new file mode 100644 index 000000000..6835fe20b --- /dev/null +++ b/src/uis/nvcf-ui/THIRD-PARTY.txt @@ -0,0 +1,624 @@ +NVCF UI — Third-Party Notices and Attributions +=============================================== + +This file lists all open-source components bundled or distributed with +NVCF UI, the licenses under which they are used, and any applicable +notices. NVIDIA copyright notices are included per NOTICE and per +per-file SPDX headers throughout the source tree. + +------------------------------------------------------------------------------- +NVIDIA CORPORATION & AFFILIATES +------------------------------------------------------------------------------- + +NVCF UI +Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +Licensed under the Apache License, Version 2.0 — see LICENSE. + +------------------------------------------------------------------------------- +Go Backend Dependencies +------------------------------------------------------------------------------- + +github.com/beorn7/perks + Version: v1.0.1 + License: MIT + Homepage: https://github.com/beorn7/perks + Notice: Copyright 2012-2015 The Prometheus Authors + Copyright 2013-2015 Blake Mizerany, Björn Rabenstein + Copyright 2010 The Go Authors + Copyright 2013 Matt T. Proud (BSD-style) + +github.com/cespare/xxhash/v2 + Version: v2.3.0 + License: MIT + Homepage: https://github.com/cespare/xxhash + +github.com/davecgh/go-spew + Version: v1.1.2-0.20180830191138-d8f796af33cc + License: ISC + Homepage: https://github.com/davecgh/go-spew + Notice: Copyright (c) 2012-2016 Dave Collins + +github.com/emicklei/go-restful/v3 + Version: v3.13.0 + License: MIT + Homepage: https://github.com/emicklei/go-restful + +github.com/evanphx/json-patch/v5 + Version: v5.9.11 + License: BSD-3-Clause + Homepage: https://github.com/evanphx/json-patch + Notice: Copyright (c) 2014, Evan Phoenix + +github.com/fsnotify/fsnotify + Version: v1.10.1 + License: BSD-3-Clause + Homepage: https://github.com/fsnotify/fsnotify + Notice: Copyright (c) 2012 The Go Authors. All rights reserved. + Copyright (c) 2012-2019 fsnotify Authors. All rights reserved. + +github.com/fxamacker/cbor/v2 + Version: v2.9.0 + License: MIT + Homepage: https://github.com/fxamacker/cbor + +github.com/go-logr/logr + Version: v1.4.3 + License: Apache-2.0 + Homepage: https://github.com/go-logr/logr + +github.com/go-openapi/jsonpointer + Version: v0.21.0 + License: Apache-2.0 + Homepage: https://github.com/go-openapi/jsonpointer + +github.com/go-openapi/jsonreference + Version: v0.20.2 + License: Apache-2.0 + Homepage: https://github.com/go-openapi/jsonreference + +github.com/go-openapi/swag + Version: v0.23.0 + License: Apache-2.0 + Homepage: https://github.com/go-openapi/swag + +github.com/google/gnostic-models + Version: v0.7.0 + License: Apache-2.0 + Homepage: https://github.com/google/gnostic + +github.com/google/uuid + Version: v1.6.0 + License: BSD-3-Clause + Homepage: https://github.com/google/uuid + Notice: Copyright (c) 2018 Google Inc. All rights reserved. + +github.com/josharian/intern + Version: v1.0.0 + License: MIT + Homepage: https://github.com/josharian/intern + +github.com/json-iterator/go + Version: v1.1.12 + License: MIT + Homepage: https://github.com/json-iterator/go + +github.com/mailru/easyjson + Version: v0.7.7 + License: MIT + Homepage: https://github.com/mailru/easyjson + +github.com/mattn/go-colorable + Version: v0.1.14 + License: MIT + Homepage: https://github.com/mattn/go-colorable + Notice: Copyright (c) 2016 Yasuhiro Matsumoto + +github.com/mattn/go-isatty + Version: v0.0.20 + License: MIT + Homepage: https://github.com/mattn/go-isatty + Notice: Copyright (c) Yasuhiro MATSUMOTO + +github.com/modern-go/concurrent + Version: v0.0.0-20180306012644-bacd9c7ef1dd + License: Apache-2.0 + Homepage: https://github.com/modern-go/concurrent + +github.com/modern-go/reflect2 + Version: v1.0.3-0.20250322232337-35a7c28c31ee + License: Apache-2.0 + Homepage: https://github.com/modern-go/reflect2 + +github.com/munnerz/goautoneg + Version: v0.0.0-20191010083416-a7dc8b61c822 + License: BSD-3-Clause + Homepage: https://github.com/munnerz/goautoneg + +github.com/pmezard/go-difflib + Version: v1.0.1-0.20181226105442-5d4384ee4fb2 + License: BSD-3-Clause + Homepage: https://github.com/pmezard/go-difflib + +github.com/prometheus/client_golang + Version: v1.23.2 + License: Apache-2.0 + Homepage: https://github.com/prometheus/client_golang + Notice: Copyright 2012-2015 The Prometheus Authors + Copyright 2013-2015 Blake Mizerany, Björn Rabenstein + Copyright 2010 The Go Authors + Copyright 2013 Matt T. Proud (BSD-style) + +github.com/prometheus/client_model + Version: v0.6.2 + License: Apache-2.0 + Homepage: https://github.com/prometheus/client_model + +github.com/prometheus/common + Version: v0.67.5 + License: Apache-2.0 + Homepage: https://github.com/prometheus/common + +github.com/prometheus/procfs + Version: v0.19.2 + License: Apache-2.0 + Homepage: https://github.com/prometheus/procfs + Notice: Copyright 2014-2015 The Prometheus Authors + +github.com/rs/xid + Version: v1.6.0 + License: MIT + Homepage: https://github.com/rs/xid + +github.com/rs/zerolog + Version: v1.35.1 + License: MIT + Homepage: https://github.com/rs/zerolog + +github.com/spf13/pflag + Version: v1.0.9 + License: BSD-3-Clause + Homepage: https://github.com/spf13/pflag + Notice: Copyright (c) 2012 Alex Ogier. All rights reserved. + Copyright (c) 2012 The Go Authors. All rights reserved. + +github.com/x448/float16 + Version: v0.8.4 + License: MIT + Homepage: https://github.com/x448/float16 + +go.yaml.in/yaml/v2 + Version: v2.4.3 + License: MIT and Apache-2.0 + Homepage: https://github.com/go-yaml/yaml + +go.yaml.in/yaml/v3 + Version: v3.0.4 + License: MIT and Apache-2.0 + Homepage: https://github.com/go-yaml/yaml + +golang.org/x/net + Version: v0.49.0 + License: BSD-3-Clause + Homepage: https://golang.org/x/net + Notice: Copyright (c) 2009 The Go Authors. All rights reserved. + +golang.org/x/oauth2 + Version: v0.34.0 + License: BSD-3-Clause + Homepage: https://golang.org/x/oauth2 + +golang.org/x/sync + Version: v0.19.0 + License: BSD-3-Clause + Homepage: https://golang.org/x/sync + +golang.org/x/sys + Version: v0.40.0 + License: BSD-3-Clause + Homepage: https://golang.org/x/sys + +golang.org/x/term + Version: v0.39.0 + License: BSD-3-Clause + Homepage: https://golang.org/x/term + +golang.org/x/text + Version: v0.33.0 + License: BSD-3-Clause + Homepage: https://golang.org/x/text + +golang.org/x/time + Version: v0.14.0 + License: BSD-3-Clause + Homepage: https://golang.org/x/time + +gomodules.xyz/jsonpatch/v2 + Version: v2.4.0 + License: Apache-2.0 + Homepage: https://github.com/gomodules/jsonpatch + +google.golang.org/protobuf + Version: v1.36.12-0.20260120151049-f2248ac996af + License: BSD-3-Clause + Homepage: https://github.com/protocolbuffers/protobuf-go + Notice: Copyright (c) 2018 The Go Authors. All rights reserved. + +gopkg.in/evanphx/json-patch.v4 + Version: v4.13.0 + License: BSD-3-Clause + Homepage: https://github.com/evanphx/json-patch + +gopkg.in/inf.v0 + Version: v0.9.1 + License: BSD-3-Clause + Homepage: https://github.com/go-inf/inf + +gopkg.in/yaml.v3 + Version: v3.0.1 + License: MIT and Apache-2.0 + Homepage: https://github.com/go-yaml/yaml + +k8s.io/api + Version: v0.36.0 + License: Apache-2.0 + Homepage: https://github.com/kubernetes/api + Notice: Copyright 2014 The Kubernetes Authors. + +k8s.io/apimachinery + Version: v0.36.0 + License: Apache-2.0 + Homepage: https://github.com/kubernetes/apimachinery + Notice: Copyright 2014 The Kubernetes Authors. + +k8s.io/apiextensions-apiserver + Version: v0.36.0 + License: Apache-2.0 + Homepage: https://github.com/kubernetes/apiextensions-apiserver + Notice: Copyright 2017 The Kubernetes Authors. + +k8s.io/client-go + Version: v0.36.0 + License: Apache-2.0 + Homepage: https://github.com/kubernetes/client-go + Notice: Copyright 2014 The Kubernetes Authors. + +k8s.io/klog/v2 + Version: v2.140.0 + License: Apache-2.0 + Homepage: https://github.com/kubernetes/klog + +k8s.io/kube-openapi + Version: v0.0.0-20260317180543-43fb72c5454a + License: Apache-2.0 + Homepage: https://github.com/kubernetes/kube-openapi + Notice: Copyright 2016 The Kubernetes Authors. + +k8s.io/utils + Version: v0.0.0-20260210185600-b8788abfbbc2 + License: Apache-2.0 + Homepage: https://github.com/kubernetes/utils + +sigs.k8s.io/controller-runtime + Version: v0.24.1 + License: Apache-2.0 + Homepage: https://github.com/kubernetes-sigs/controller-runtime + Notice: Copyright 2018 The Kubernetes Authors. + +sigs.k8s.io/json + Version: v0.0.0-20250730193827-2d320260d730 + License: Apache-2.0 and BSD-3-Clause + Homepage: https://github.com/kubernetes-sigs/json + +sigs.k8s.io/randfill + Version: v1.0.0 + License: Apache-2.0 and BSD-3-Clause + Homepage: https://github.com/kubernetes-sigs/randfill + +sigs.k8s.io/structured-merge-diff/v6 + Version: v6.3.2 + License: Apache-2.0 + Homepage: https://github.com/kubernetes-sigs/structured-merge-diff + +sigs.k8s.io/yaml + Version: v1.6.0 + License: MIT and Apache-2.0 + Homepage: https://github.com/kubernetes-sigs/yaml + +------------------------------------------------------------------------------- +UI (Node.js / npm) Dependencies +------------------------------------------------------------------------------- + +@biomejs/biome + Version: 2.4.5 + License: MIT and Apache-2.0 + Homepage: https://biomejs.dev + +@faker-js/faker + Version: ^10.4.0 + License: MIT + Homepage: https://fakerjs.dev + +@nvidia/foundations-react-core + Version: ^1.7.0 + License: NVIDIA Proprietary + Homepage: https://www.nvidia.com + Notice: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + Distributed under NVIDIA's software license agreement. + +@nvidia/foundations-tailwind-plugin + Version: ^1.7.0 + License: NVIDIA Proprietary + Homepage: https://www.nvidia.com + Notice: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + Distributed under NVIDIA's software license agreement. + +@tailwindcss/typography + Version: ^0.5.16 + License: MIT + Homepage: https://github.com/tailwindlabs/tailwindcss-typography + Notice: Copyright (c) Tailwind Labs, Inc. + +@tailwindcss/vite + Version: ^4.1.18 + License: MIT + Homepage: https://tailwindcss.com + +@tanstack/react-devtools + Version: latest + License: MIT + Homepage: https://tanstack.com + +@tanstack/react-query + Version: ^5.100.1 + License: MIT + Homepage: https://tanstack.com/query + Notice: Copyright (c) 2021-present Tanner Linsley + +@tanstack/react-query-devtools + Version: ^5.100.0 + License: MIT + Homepage: https://tanstack.com/query + +@tanstack/react-router + Version: latest + License: MIT + Homepage: https://tanstack.com/router + Notice: Copyright (c) 2021-present Tanner Linsley + +@tanstack/react-router-devtools + Version: latest + License: MIT + Homepage: https://tanstack.com/router + +@tanstack/react-table + Version: ^8.21.3 + License: MIT + Homepage: https://tanstack.com/table + Notice: Copyright (c) 2021-present Tanner Linsley + +@tanstack/devtools-vite + Version: latest + License: MIT + Homepage: https://tanstack.com + +@testing-library/dom + Version: ^10.4.1 + License: MIT + Homepage: https://testing-library.com + Notice: Copyright (c) 2017-present Kent C. Dodds + +@testing-library/jest-dom + Version: ^6.9.1 + License: MIT + Homepage: https://github.com/testing-library/jest-dom + +@testing-library/react + Version: ^16.3.0 + License: MIT + Homepage: https://testing-library.com/react + +@testing-library/user-event + Version: ^14.6.1 + License: MIT + Homepage: https://github.com/testing-library/user-event + +@types/lodash-es + Version: ^4.17.12 + License: MIT + Homepage: https://github.com/DefinitelyTyped/DefinitelyTyped + +@types/node + Version: ^22.10.2 + License: MIT + Homepage: https://github.com/DefinitelyTyped/DefinitelyTyped + +@types/react + Version: ^19.2.0 + License: MIT + Homepage: https://github.com/DefinitelyTyped/DefinitelyTyped + +@types/react-dom + Version: ^19.2.0 + License: MIT + Homepage: https://github.com/DefinitelyTyped/DefinitelyTyped + +@vitejs/plugin-react + Version: ^6.0.1 + License: MIT + Homepage: https://github.com/vitejs/vite-plugin-react + +@vitest/coverage-v8 + Version: 4.1.5 + License: MIT + Homepage: https://vitest.dev + +clsx + Version: ^2.1.1 + License: MIT + Homepage: https://github.com/lukeed/clsx + Notice: Copyright (c) Luke Edwards (lukeed.com) + +dayjs + Version: ^1.11.21 + License: MIT + Homepage: https://day.js.org + Notice: Copyright (c) 2018-present, iamkun + +happy-dom + Version: ^20.9.0 + License: MIT + Homepage: https://github.com/capricorn86/happy-dom + +lodash-es + Version: ^4.18.1 + License: MIT + Homepage: https://lodash.com + Notice: Copyright OpenJS Foundation and other contributors + Based on Underscore.js, copyright Jeremy Ashkenas, + DocumentCloud and Investigative Reporters & Editors + +lucide-react + Version: ^1.3.0 + License: ISC + Homepage: https://lucide.dev + Notice: Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 + as part of Feather (MIT). All other copyright (c) for Lucide are held + by Lucide Contributors 2022. + +msw + Version: ^2.13.6 + License: MIT + Homepage: https://mswjs.io + Notice: Copyright (c) 2018-present Artem Zakharchenko + +orval + Version: ^8.9.0 + License: MIT + Homepage: https://orval.dev + +react + Version: ^19.2.0 + License: MIT + Homepage: https://react.dev + Notice: Copyright (c) Meta Platforms, Inc. and affiliates. + +react-dom + Version: ^19.2.0 + License: MIT + Homepage: https://react.dev + Notice: Copyright (c) Meta Platforms, Inc. and affiliates. + +react-error-boundary + Version: ^6.1.2 + License: MIT + Homepage: https://github.com/bvaughn/react-error-boundary + Notice: Copyright (c) 2022 Brian Vaughn + +rollup-plugin-visualizer + Version: ^7.0.1 + License: MIT + Homepage: https://github.com/btd/rollup-plugin-visualizer + +tailwindcss + Version: ^4.1.18 + License: MIT + Homepage: https://tailwindcss.com + Notice: Copyright (c) Tailwind Labs, Inc. + +typescript + Version: ^5.7.2 + License: Apache-2.0 + Homepage: https://www.typescriptlang.org + Notice: Copyright (c) Microsoft Corporation. All rights reserved. + +vite + Version: ^8.0.0 + License: MIT + Homepage: https://vite.dev + Notice: Copyright (c) 2019-present, VoidZero Inc. and Vite contributors + +vitest + Version: ^4.1.5 + License: MIT + Homepage: https://vitest.dev + Notice: Copyright (c) 2021-present Matias Capeletto, Antfu and Vitest + contributors + +zod + Version: ^4.3.6 + License: MIT + Homepage: https://zod.dev + Notice: Copyright (c) 2020 Colin McDonnell + +------------------------------------------------------------------------------- +Build System and Container Tooling +------------------------------------------------------------------------------- + +Bazel / Bazelisk + License: Apache-2.0 + Homepage: https://bazel.build + Notice: Copyright 2016 The Bazel Authors. All rights reserved. + +rules_oci + Version: 2.2.7 + License: Apache-2.0 + Homepage: https://github.com/bazel-contrib/rules_oci + +rules_pkg + Version: 1.2.0 + License: Apache-2.0 + Homepage: https://github.com/bazelbuild/rules_pkg + Notice: Copyright 2021 The Bazel Authors. All rights reserved. + +rules_go + Version: 0.60.0 + License: Apache-2.0 + Homepage: https://github.com/bazel-contrib/rules_go + Notice: Copyright 2014 The Bazel Authors. All rights reserved. + +gazelle + Version: 0.48.0 + License: Apache-2.0 + Homepage: https://github.com/bazelbuild/bazel-gazelle + Notice: Copyright 2017 The Bazel Authors. All rights reserved. + +aspect_bazel_lib + Version: 2.22.5 + License: Apache-2.0 + Homepage: https://github.com/aspect-build/bazel-lib + +hermetic_cc_toolchain + Version: 4.1.0 + License: Apache-2.0 + Homepage: https://github.com/uber/hermetic_cc_toolchain + +platforms + Version: 1.0.0 + License: Apache-2.0 + Homepage: https://github.com/bazelbuild/platforms + Notice: Copyright 2019 The Bazel Authors. All rights reserved. + +rules_python + Version: 1.4.1 + License: Apache-2.0 + Homepage: https://github.com/bazelbuild/rules_python + Notice: Copyright 2017 The Bazel Authors. All rights reserved. + +distroless/static + License: Apache-2.0 + Homepage: https://github.com/GoogleContainerTools/distroless + Notice: Copyright 2017 Google Inc. All rights reserved. + +------------------------------------------------------------------------------- +License Texts +------------------------------------------------------------------------------- + +Full license texts for all licenses referenced above are available at: + + Apache-2.0 — https://www.apache.org/licenses/LICENSE-2.0 + MIT — https://opensource.org/licenses/MIT + BSD-3-Clause — https://opensource.org/licenses/BSD-3-Clause + ISC — https://opensource.org/licenses/ISC + +NVIDIA proprietary components are distributed under NVIDIA's software +license agreement and are not open-source. Contact NVIDIA for terms. diff --git a/src/uis/nvcf-ui/Taskfile.yml b/src/uis/nvcf-ui/Taskfile.yml new file mode 100644 index 000000000..ac449d733 --- /dev/null +++ b/src/uis/nvcf-ui/Taskfile.yml @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: "3" + +includes: + ui: + taskfile: ./ui/Taskfile.yml + dir: ./ui + backend: + taskfile: ./backend/Taskfile.yml + dir: ./backend + +tasks: + setup: + desc: Bootstrap dev environment + cmds: + - go install github.com/google/addlicense@v1.2.0 + - task: ui:install + - task: backend:install + + generate: + desc: Generate typed API hooks, TypeScript models, and MSW mock handlers from OpenAPI spec + cmds: + - task: ui:generate + - task: license:add + + typecheck: + desc: Type-check the UI + cmds: + - task: ui:typecheck + + build: + desc: Build UI into the backend static dir and compile the Go binaries + cmds: + - task: ui:build + vars: + OUT_DIR: ../backend/static + - task: backend:build + + test: + desc: Run all tests + cmds: + - task: ui:test + - task: backend:test + + lint: + desc: Run all linters + cmds: + - task: ui:lint + - task: backend:lint + - task: license:check + + lint:fix: + desc: Auto-fix all lint issues + cmds: + - task: ui:lint:fix + - task: backend:lint:fix + - task: license:add + + license:check: + desc: Check for missing license headers + cmds: + - task: license:add + vars: + FLAGS: -check + + license:add: + desc: Add missing license headers + cmds: + - addlicense {{.FLAGS}} -f license-header.txt -ignore "**/node_modules/**" -ignore "**/pnpm-lock.yaml" -ignore "backend/static/**" -ignore "backend/bin/**" -ignore "ui/dist/**" -ignore "bazel-*/**" . diff --git a/src/uis/nvcf-ui/backend/.gitignore b/src/uis/nvcf-ui/backend/.gitignore new file mode 100644 index 000000000..d15249a05 --- /dev/null +++ b/src/uis/nvcf-ui/backend/.gitignore @@ -0,0 +1,2 @@ +bin/ +static/ diff --git a/src/uis/nvcf-ui/backend/.golangci.yml b/src/uis/nvcf-ui/backend/.golangci.yml new file mode 100644 index 000000000..07e3aaeee --- /dev/null +++ b/src/uis/nvcf-ui/backend/.golangci.yml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: "2" + +linters: + default: none + enable: + - errcheck + - govet + - staticcheck + - unused + +formatters: + enable: + - gofmt \ No newline at end of file diff --git a/src/uis/nvcf-ui/backend/BUILD.bazel b/src/uis/nvcf-ui/backend/BUILD.bazel new file mode 100644 index 000000000..e089f5e99 --- /dev/null +++ b/src/uis/nvcf-ui/backend/BUILD.bazel @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# gazelle:prefix github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend +# gazelle:exclude static +# gazelle:exclude bin + +# All backend source files for the source compliance bundle. Aggregates from +# sub-packages because glob() does not cross package boundaries in Bazel. +filegroup( + name = "sources", + srcs = glob( + ["*.go", "go.mod", "go.sum"], + allow_empty = True, + ) + [ + "//backend/cmd/control-plane:sources", + "//backend/cmd/server:sources", + "//backend/internal/control-plane:sources", + "//backend/internal/middleware:sources", + "//backend/internal/token-watcher:sources", + "//backend/internal/utils:sources", + ], + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/backend/Taskfile.yml b/src/uis/nvcf-ui/backend/Taskfile.yml new file mode 100644 index 000000000..cae4da3f6 --- /dev/null +++ b/src/uis/nvcf-ui/backend/Taskfile.yml @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: "3" + +tasks: + install: + desc: Download Go module dependencies + cmds: + - go mod download + - go mod tidy + + build: + desc: Compile the server and control-plane binaries into bin/ + cmds: + - go build -o bin/server ./cmd/server + - go build -o bin/control-plane ./cmd/control-plane + + lint: + desc: Lint Go code + cmds: + - golangci-lint run ./... + + lint:fix: + desc: Auto-fix Go lint issues + cmds: + - golangci-lint run --fix ./... + + test: + desc: Run Go tests + cmds: + - go test ./... diff --git a/src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel b/src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel new file mode 100644 index 000000000..66dc21f14 --- /dev/null +++ b/src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "control-plane_lib", + srcs = ["main.go"], + importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/cmd/control-plane", + visibility = ["//visibility:private"], + deps = [ + "//backend/internal/control-plane", + "//backend/internal/utils", + "@io_k8s_sigs_controller_runtime//pkg/manager/signals", + ], +) + +go_binary( + name = "control-plane", + embed = [":control-plane_lib"], + visibility = ["//visibility:public"], +) + + +filegroup( + name = "sources", + srcs = glob(["*.go", "*.md"], allow_empty = True), + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/backend/cmd/control-plane/main.go b/src/uis/nvcf-ui/backend/cmd/control-plane/main.go new file mode 100644 index 000000000..fb1e71433 --- /dev/null +++ b/src/uis/nvcf-ui/backend/cmd/control-plane/main.go @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + + "sigs.k8s.io/controller-runtime/pkg/manager/signals" + + cplane "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/control-plane" + "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/utils" +) + +const ( + componentsPath = "COMPONENTS_PATH" + defaultComponentsPath = "/etc/backend/components.yaml" +) + +func main() { + // liveness re-purposes this same binary as a Kubernetes exec liveness probe + // (no HTTP server): the kubelet runs `control-plane -liveness`, which checks + // the monitor's heartbeat is fresh and exits non-zero if it is stale. + liveness := flag.Bool("liveness", false, "check the monitor heartbeat is fresh and exit (Kubernetes exec liveness probe)") + flag.Parse() + + logger := utils.ConfigLogger() + + if *liveness { + if err := cplane.CheckLiveness(); err != nil { + logger.Fatal().Err(err).Msg("liveness check failed") + } + return + } + + ctx := signals.SetupSignalHandler() + ctx = logger.WithContext(ctx) + + k8sclient, err := utils.InitK8sClient() + if err != nil { + logger.Fatal().Err(err).Msg("Failed to initialize K8s client") + } + + monitor := cplane.New(k8sclient) + if err = monitor.Loadcomponents(utils.GetEnvOr(componentsPath, defaultComponentsPath)); err != nil { + logger.Fatal().Err(err).Msg("Failed to load components") + } + monitor.RunHealthChecks(ctx) +} diff --git a/src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel b/src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel new file mode 100644 index 000000000..07619c734 --- /dev/null +++ b/src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "server_lib", + srcs = ["main.go"], + importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/cmd/server", + visibility = ["//visibility:private"], + deps = [ + "//backend/internal/control-plane", + "//backend/internal/middleware", + "//backend/internal/token-watcher", + "//backend/internal/utils", + "@com_github_rs_zerolog//:zerolog", + "@com_github_rs_zerolog//hlog", + "@io_k8s_sigs_controller_runtime//pkg/manager/signals", + ], +) + +go_binary( + name = "server", + embed = [":server_lib"], + visibility = ["//visibility:public"], +) + + +filegroup( + name = "sources", + srcs = glob(["*.go", "*.md"], allow_empty = True), + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/backend/cmd/server/main.go b/src/uis/nvcf-ui/backend/cmd/server/main.go new file mode 100644 index 000000000..3f9a0dd14 --- /dev/null +++ b/src/uis/nvcf-ui/backend/cmd/server/main.go @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "errors" + "fmt" + "io/fs" + "net/http" + "net/http/httputil" + "net/url" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/hlog" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" + + cplane "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/control-plane" + "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/middleware" + twatcher "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/token-watcher" + "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/utils" +) + +const ( + defaultNVCFURL = "api.nvcf.svc.cluster.local:8080" + defaultNVCTURL = "nvct-api.nvcf.svc.cluster.local:8080" + defaultSISURL = "api.sis.svc.cluster.local:8080" + defaultServerPort = "8300" + defaultStaticDir = "static" + + nvcfURL = "NVCF_URL" + nvctURL = "NVCT_URL" + sisURL = "SIS_URL" + serverPort = "SERVER_PORT" + staticDir = "STATIC_DIR" +) + +func main() { + ctx := signals.SetupSignalHandler() + logger := utils.ConfigLogger() + ctx = logger.WithContext(ctx) + + portStr := utils.GetEnvOr(serverPort, defaultServerPort) + port, err := strconv.Atoi(portStr) + if err != nil { + logger.Fatal().Err(err).Msgf("Invalid %s", serverPort) + } + tokenWatcher := twatcher.Watch(ctx) + k8sClient, err := utils.InitK8sClient() + if err != nil { + logger.Fatal().Err(err).Msg("Failed to initialize K8s client") + } + + router := http.NewServeMux() + var handler http.Handler = router + mdwChain := []func(http.Handler) http.Handler{ + middleware.AllowReadMethods, + hlog.AccessHandler( + func(r *http.Request, status, _ int, duration time.Duration) { + if r.Method == http.MethodGet { + return + } + hlog.FromRequest(r).Info(). + Int("status", status). + Str("latency", duration.String()). + Msg("Exit Audit") + }, + ), + middleware.EntryAudit, + hlog.RequestHandler("url"), + hlog.RemoteAddrHandler("client_ip"), + middleware.AddRequestId, + middleware.PanicRecovery, + hlog.NewHandler(logger), + } + for i := range mdwChain { + handler = mdwChain[i](handler) + } + + // One reverse proxy per upstream. SetURL leaves the request path unchanged, + // so /v2/nvcf/... -> NVCF, /v1/nvct/... -> NVCT, /v1/si/... -> the cluster API. + nvcfURL := &url.URL{Scheme: "http", Host: utils.GetEnvOr(nvcfURL, defaultNVCFURL)} + nvctURL := &url.URL{Scheme: "http", Host: utils.GetEnvOr(nvctURL, defaultNVCTURL)} + sisURL := &url.URL{Scheme: "http", Host: utils.GetEnvOr(sisURL, defaultSISURL)} + + nvcfProxy := newProxy(nvcfURL, tokenWatcher.NVCFToken, logger) + router.Handle("/v2/nvcf/accounts", nvcfProxy) + router.Handle("/v2/nvcf/accounts/", nvcfProxy) + router.Handle("/v1/nvct/accounts/", newProxy(nvctURL, tokenWatcher.NVCTToken, logger)) + router.Handle("/v1/si/accounts/", newProxy(sisURL, tokenWatcher.SISToken, logger)) + + // Liveness probe: respond 200 to GET /status with no upstream dependency. + router.HandleFunc("GET /status", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + // Control-plane component health, read from the ConfigMap the monitor writes. + router.Handle("GET /v1/control-plane", cplane.StatusHandler(k8sClient)) + + // Serve the static UI build. Every file is registered as its own route at + // startup and the "/" subtree pattern serves index.html for anything else. + registerStatic(router, utils.GetEnvOr(staticDir, defaultStaticDir), logger) + + server := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", port), + Handler: handler, + } + + go func() { + logger.Info().Msgf("Started server on port %d", port) + if errS := server.ListenAndServe(); errS != nil && + !errors.Is(errS, http.ErrServerClosed) { + logger.Fatal().Err(errS).Msg("Failed to start server") + } + }() + + <-ctx.Done() + defer tokenWatcher.Wait() + if err := server.Shutdown(context.Background()); err != nil { + logger.Fatal().Err(err).Msg("Failed to shutdown server") + } +} + +// newProxy returns a handler that reverse-proxies requests unchanged to target, +// injecting target's bearer token (from token) as the Authorization header. If +// this upstream has no valid token, the request is rejected with 500 rather than +// forwarded (the upstream would reject it anyway). It serves any HTTP method. +func newProxy(target *url.URL, token func() (string, bool), logger zerolog.Logger) http.Handler { + proxy := &httputil.ReverseProxy{ + Rewrite: func(pr *httputil.ProxyRequest) { + pr.SetURL(target) + pr.SetXForwarded() + pr.Out.Host = target.Host + }, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + if errors.Is(err, context.Canceled) { // client went away; nothing to send + return + } + logger.Error().Err(err).Msgf("upstream %s error", target.Host) + w.WriteHeader(http.StatusBadGateway) + }, + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t, ok := token() + if !ok || t == "" { + w.WriteHeader(http.StatusInternalServerError) + return + } + r.Header.Set("Authorization", "Bearer "+t) + proxy.ServeHTTP(w, r) + }) +} + +// registerStatic wires the static UI build under root into router. +// +// The file tree is walked once at startup and every file becomes an explicit +// route, so the mux dispatches requests directly instead of touching the +// filesystem to decide whether a path exists. The "/" pattern is a subtree +// match, so it doubles as the not-found handler: any path the mux can't map to +// a real file (client-side routes like /clusters, plus the root itself) serves +// index.html, giving the SPA its deep links. +// +// Caching is split by asset type: Vite emits content-hashed files under +// assets/, safe to cache immutably forever, while index.html must never be +// cached so a redeploy is picked up immediately and browsers don't keep +// referencing assets that no longer exist. +func registerStatic(router *http.ServeMux, root string, logger zerolog.Logger) { + err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + rel, err := filepath.Rel(root, p) + if err != nil { + return err + } + urlPath := "/" + filepath.ToSlash(rel) + if urlPath == "/index.html" { // served by the "/" fallback below + return nil + } + cacheControl := "no-cache" + if strings.HasPrefix(urlPath, "/assets/") { + cacheControl = "public, max-age=31536000, immutable" + } + router.Handle(urlPath, staticFile(p, cacheControl)) + return nil + }) + if err != nil { + logger.Warn().Err(err).Str("dir", root). + Msg("Failed to index static dir; only the SPA fallback will be served") + } + + // Not-found handler: unmatched paths (and "/") get index.html, never cached. + router.Handle("/", staticFile(filepath.Join(root, "index.html"), "no-store")) +} + +// staticFile serves a single file from disk with the given Cache-Control value. +// Static assets are read-only, so only GET and HEAD are served; any other +// method gets 405. +func staticFile(path, cacheControl string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet && r.Method != http.MethodHead { + w.Header().Set("Allow", "GET, HEAD") + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + w.Header().Set("Cache-Control", cacheControl) + http.ServeFile(w, r, path) + }) +} diff --git a/src/uis/nvcf-ui/backend/go.mod b/src/uis/nvcf-ui/backend/go.mod new file mode 100644 index 000000000..9c52fd6e7 --- /dev/null +++ b/src/uis/nvcf-ui/backend/go.mod @@ -0,0 +1,65 @@ +module github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend + +go 1.26.2 + +require ( + github.com/fsnotify/fsnotify v1.10.1 + github.com/rs/zerolog v1.35.1 + go.yaml.in/yaml/v3 v3.0.4 + k8s.io/api v0.36.0 + k8s.io/apimachinery v0.36.0 + k8s.io/client-go v0.36.0 + sigs.k8s.io/controller-runtime v0.24.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.14.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.36.0 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/src/uis/nvcf-ui/backend/go.sum b/src/uis/nvcf-ui/backend/go.sum new file mode 100644 index 000000000..b8ca5c167 --- /dev/null +++ b/src/uis/nvcf-ui/backend/go.sum @@ -0,0 +1,180 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= +github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= +github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= +k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= +k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= +k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= +k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= +k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= +k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= +k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= +sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel new file mode 100644 index 000000000..a1064d4aa --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "control-plane", + srcs = [ + "handler.go", + "health.go", + "heartbeat.go", + "httphealth.go", + "k8shealth.go", + ], + importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/control-plane", + visibility = ["//backend:__subpackages__"], + deps = [ + "//backend/internal/utils", + "@com_github_rs_zerolog//:zerolog", + "@in_yaml_go_yaml_v3//:yaml", + "@io_k8s_api//apps/v1:apps", + "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/api/errors", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_apimachinery//pkg/labels", + "@io_k8s_apimachinery//pkg/types", + "@io_k8s_client_go//applyconfigurations/core/v1:core", + "@io_k8s_sigs_controller_runtime//pkg/client", + ], +) + +go_test( + name = "control-plane_test", + srcs = [ + "handler_test.go", + "health_test.go", + "heartbeat_test.go", + "k8shealth_test.go", + ], + embed = [":control-plane"], + deps = [ + "@in_yaml_go_yaml_v3//:yaml", + "@io_k8s_api//apps/v1:apps", + "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_apimachinery//pkg/runtime", + "@io_k8s_client_go//kubernetes/scheme", + "@io_k8s_sigs_controller_runtime//pkg/client", + "@io_k8s_sigs_controller_runtime//pkg/client/fake", + "@io_k8s_sigs_controller_runtime//pkg/client/interceptor", + ], +) + + +filegroup( + name = "sources", + srcs = glob(["*.go", "*.md"], allow_empty = True), + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/handler.go b/src/uis/nvcf-ui/backend/internal/control-plane/handler.go new file mode 100644 index 000000000..0b1339059 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/handler.go @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "encoding/json" + "net/http" + "sort" + "time" + + "github.com/rs/zerolog" + "go.yaml.in/yaml/v3" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ControlPlaneComponentStatus is the JSON shape served by StatusHandler. It +// mirrors the ControlPlaneComponentStatus schema in spec/openapi.yaml. +type ControlPlaneComponentStatus struct { + ComponentName string `json:"componentName"` + Namespace string `json:"namespace"` + Status string `json:"status"` + Timestamp string `json:"timestamp"` +} + +// StatusHandler serves the latest control-plane component health statuses as a +// JSON array (GET /v1/control-plane). It reads them from the control-plane health +// ConfigMap — the same ConfigMap RunHealthChecks writes — where each data key is a +// component name and its value is a YAML statusEntry. +func StatusHandler(k8sClient client.Client) http.Handler { + namespace := configMapNamespace() + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger := zerolog.Ctx(r.Context()) + + var cm corev1.ConfigMap + nn := types.NamespacedName{Namespace: namespace, Name: controlPlaneHealthCM} + if err := k8sClient.Get(r.Context(), nn, &cm); err != nil { + logger.Error().Err(err).Msgf("failed to read configmap %s/%s", namespace, controlPlaneHealthCM) + w.WriteHeader(http.StatusInternalServerError) + return + } + + statuses := make([]ControlPlaneComponentStatus, 0, len(cm.Data)) + for name, raw := range cm.Data { + var entry statusEntry + if err := yaml.Unmarshal([]byte(raw), &entry); err != nil { + logger.Error().Err(err).Msgf("failed to parse status for component %s", name) + w.WriteHeader(http.StatusInternalServerError) + return + } + statuses = append(statuses, ControlPlaneComponentStatus{ + ComponentName: name, + Namespace: entry.Namespace, + Status: string(entry.Status), + Timestamp: entry.Timestamp.UTC().Format(time.RFC3339), + }) + } + + // ConfigMap data is an unordered map; sort by name for a stable response. + sort.Slice(statuses, func(i, j int) bool { + return statuses[i].ComponentName < statuses[j].ComponentName + }) + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(statuses); err != nil { + logger.Error().Err(err).Msg("failed to encode control-plane status response") + } + }) +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/handler_test.go b/src/uis/nvcf-ui/backend/internal/control-plane/handler_test.go new file mode 100644 index 000000000..e6c4b0d4e --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/handler_test.go @@ -0,0 +1,144 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" +) + +// StatusHandler resolves the namespace internally via configMapNamespace, so +// tests place the ConfigMap in that same namespace (env unset → defaultNamespace). +var testNS = defaultNamespace + +func buildScheme(t *testing.T) *runtime.Scheme { + t.Helper() + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + return scheme +} + +func healthCM(data map[string]string) *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: controlPlaneHealthCM, Namespace: testNS}, + Data: data, + } +} + +func TestStatusHandler(t *testing.T) { + t.Parallel() + + const ( + healthyYAML = "namespace: sis\nstatus: healthy\ntimestamp: 2026-06-28T14:30:00Z\n" + unhealthyYAML = "namespace: cassandra-system\nstatus: unhealthy\ntimestamp: 2026-06-28T14:30:00Z\n" + ) + + tests := []struct { + name string + objects []client.Object + getErr bool + wantStatus int + want []ControlPlaneComponentStatus + }{ + { + name: "returns sorted statuses from configmap", + objects: []client.Object{healthCM(map[string]string{"sis-api": healthyYAML, "cassandra": unhealthyYAML})}, + wantStatus: http.StatusOK, + want: []ControlPlaneComponentStatus{ + {ComponentName: "cassandra", Namespace: "cassandra-system", Status: "unhealthy", Timestamp: "2026-06-28T14:30:00Z"}, + {ComponentName: "sis-api", Namespace: "sis", Status: "healthy", Timestamp: "2026-06-28T14:30:00Z"}, + }, + }, + { + name: "empty configmap returns empty array", + objects: []client.Object{healthCM(map[string]string{})}, + wantStatus: http.StatusOK, + want: []ControlPlaneComponentStatus{}, + }, + { + name: "missing configmap returns 500", + objects: nil, + wantStatus: http.StatusInternalServerError, + }, + { + name: "malformed entry returns 500", + objects: []client.Object{healthCM(map[string]string{"sis-api": "timestamp: not-a-date\n"})}, + wantStatus: http.StatusInternalServerError, + }, + { + name: "client get error returns 500", + objects: []client.Object{healthCM(map[string]string{"sis-api": healthyYAML})}, + getErr: true, + wantStatus: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + builder := fake.NewClientBuilder().WithScheme(buildScheme(t)).WithObjects(tt.objects...) + if tt.getErr { + builder = builder.WithInterceptorFuncs(interceptor.Funcs{ + Get: func(context.Context, client.WithWatch, client.ObjectKey, client.Object, ...client.GetOption) error { + return context.DeadlineExceeded + }, + }) + } + + h := StatusHandler(builder.Build()) + + rr := httptest.NewRecorder() + h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/v1/control-plane", nil)) + + if rr.Code != tt.wantStatus { + t.Fatalf("status = %d, want %d (body: %s)", rr.Code, tt.wantStatus, rr.Body.String()) + } + if tt.wantStatus != http.StatusOK { + return + } + + if ct := rr.Header().Get("Content-Type"); ct != "application/json" { + t.Errorf("Content-Type = %q, want application/json", ct) + } + + var got []ControlPlaneComponentStatus + if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + if len(got) != len(tt.want) { + t.Fatalf("got %d statuses, want %d: %+v", len(got), len(tt.want), got) + } + for i := range tt.want { + if got[i] != tt.want[i] { + t.Errorf("status[%d] = %+v, want %+v", i, got[i], tt.want[i]) + } + } + }) + } +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/health.go b/src/uis/nvcf-ui/backend/internal/control-plane/health.go new file mode 100644 index 000000000..1d5e36ca8 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/health.go @@ -0,0 +1,341 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "context" + "fmt" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/rs/zerolog" + "go.yaml.in/yaml/v3" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + corev1ac "k8s.io/client-go/applyconfigurations/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/utils" +) + +var ( + // healthCheckInterval is how often RunHealthChecks probes every component. It's + // a package variable so tests can shorten the otherwise 20s cycle. + healthCheckInterval = 20 * time.Second + + controlPlaneHealthCM = "control-plane-health" + namespaceEnvVar = "NAMESPACE" + defaultNamespace = "nvcf-ui" +) + +// configMapNamespace resolves the namespace holding the control-plane health +// ConfigMap, from namespaceEnvVar or defaultNamespace. +func configMapNamespace() string { + return utils.GetEnvOr(namespaceEnvVar, defaultNamespace) +} + +type healthStatus string + +const ( + healthStatusHealthy healthStatus = "healthy" + healthStatusUnhealthy healthStatus = "unhealthy" +) + +type k8sWorkload struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + obj workloadObject // instantiated at load time based on Type +} + +// componentConfig is one entry in the components list. A component is probed +// either over HTTP (Endpoints) or by inspecting Kubernetes workloads +// (K8sWorkloads) — exactly one of the two must be set. +type componentConfig struct { + Name string `yaml:"name"` + Namespace string `yaml:"namespace"` + Endpoints []string `yaml:"endpoints"` + K8sWorkloads []k8sWorkload `yaml:"k8s_workloads"` +} + +type componentsConfig struct { + Components []componentConfig `yaml:"components"` +} + +// --- Runtime health tracking --- + +type componentHealth struct { + ComponentName string + Namespace string // namespace the component lives in; used for k8s workload lookups and reported in the status response + Status healthStatus + Timestamp time.Time + endpoints []string // HTTP endpoints; empty for k8s components + workloads []k8sWorkload // k8s workloads; empty for HTTP components +} + +// Monitor tracks and reports the health of the control-plane components. +// Its components slice is populated once by Loadcomponents before +// RunHealthChecks starts; each health-check goroutine then writes only its own +// distinct index, so concurrent status updates are race-free without locking. +type Monitor struct { + k8sClient client.Client + httpClient *http.Client + components []componentHealth +} + +// statusEntry is the per-component value stored in the ConfigMap data, +// keyed by component name. +type statusEntry struct { + Namespace string `yaml:"namespace"` + Status healthStatus `yaml:"status"` + Timestamp time.Time `yaml:"timestamp"` +} + +// New returns a Monitor that reaches the cluster through k8sClient and probes +// HTTP endpoints with a fixed-timeout client. +func New(k8sClient client.Client) *Monitor { + return &Monitor{ + k8sClient: k8sClient, + httpClient: &http.Client{Timeout: 20 * time.Second}, + } +} + +func (m *Monitor) Loadcomponents(path string) error { + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read %s: %w", path, err) + } + + var cfg componentsConfig + if err := yaml.Unmarshal(data, &cfg); err != nil { + return fmt.Errorf("parse components config: %w", err) + } + + for _, c := range cfg.Components { + name := strings.TrimSpace(c.Name) + if name == "" { + return fmt.Errorf("missing name in components entry") + } + namespace := strings.TrimSpace(c.Namespace) + if namespace == "" { + return fmt.Errorf("component %q: missing namespace", name) + } + + // A component is checked over HTTP, via k8s workloads, or both — but it + // must define at least one of the two. + hasEndpoints := len(c.Endpoints) > 0 + hasWorkloads := len(c.K8sWorkloads) > 0 + if !hasEndpoints && !hasWorkloads { + return fmt.Errorf("component %q: at least one of endpoints or k8s_workloads must be set", name) + } + + comp := componentHealth{ + ComponentName: name, + Namespace: namespace, + Status: healthStatusUnhealthy, + Timestamp: time.Now().UTC(), + } + + if hasEndpoints { + endpoints := make([]string, 0, len(c.Endpoints)) + for _, ep := range c.Endpoints { + ep = strings.TrimSpace(ep) + if err := validateFQDN(ep); err != nil { + return fmt.Errorf("component %q: %w", name, err) + } + endpoints = append(endpoints, "http://"+ep) + } + comp.endpoints = endpoints + } + + if hasWorkloads { + workloads := make([]k8sWorkload, 0, len(c.K8sWorkloads)) + for _, w := range c.K8sWorkloads { + if strings.TrimSpace(w.Name) == "" { + return fmt.Errorf("component %q: missing workload name", name) + } + t := strings.ToLower(strings.TrimSpace(w.Type)) + if !validK8sWorkloadTypes[t] { + return fmt.Errorf("component %q: invalid workload type %q, must be one of: deployment, statefulset, daemonset, replicaset", name, w.Type) + } + var obj workloadObject + switch t { + case "deployment": + obj = &deploymentWorkload{} + case "statefulset": + obj = &statefulSetWorkload{} + case "daemonset": + obj = &daemonSetWorkload{} + case "replicaset": + obj = &replicaSetWorkload{} + } + workloads = append(workloads, k8sWorkload{Name: w.Name, Type: t, obj: obj}) + } + comp.workloads = workloads + } + + m.components = append(m.components, comp) + } + + return nil +} + +func (m *Monitor) checkComponentHealth(ctx context.Context, index int) { + logger := zerolog.Ctx(ctx) + + // A component may define endpoints, workloads, or both; it is healthy only + // when every configured check passes. + var err error + if len(m.components[index].endpoints) > 0 { + err = m.checkEndpoints(ctx, index) + } + if err == nil && len(m.components[index].workloads) > 0 { + err = m.checkWorkloads(ctx, index) + } + + newStatus := healthStatusHealthy + if err != nil { + newStatus = healthStatusUnhealthy + } + if newStatus == m.components[index].Status { + return // no transition; nothing to log or update + } + + if err != nil { + logger.Error().Err(err).Msgf("component %s is unhealthy", m.components[index].ComponentName) + } else { + logger.Info().Msgf("component %s is healthy", m.components[index].ComponentName) + } + m.components[index].Status = newStatus + m.components[index].Timestamp = time.Now().UTC() +} + +// saveStatusToConfigMap writes the current component statuses to the named +// ConfigMap, with one data key per component. It uses server-side apply so a +// single call creates or fully overwrites the ConfigMap with the in-memory +// values — any manual edits are clobbered on the next run. +func (m *Monitor) saveStatusToConfigMap(ctx context.Context, namespace, name string) error { + data := make(map[string]string, len(m.components)) + for _, c := range m.components { + out, err := yaml.Marshal(statusEntry{Namespace: c.Namespace, Status: c.Status, Timestamp: c.Timestamp}) + if err != nil { + return fmt.Errorf("marshal status for %s: %w", c.ComponentName, err) + } + data[c.ComponentName] = string(out) + } + + cm := corev1ac.ConfigMap(name, namespace).WithData(data) + return m.k8sClient.Apply(ctx, cm, client.FieldOwner("backend"), client.ForceOwnership) +} + +// seedFromConfigMap restores each component's last-known Status and Timestamp +// from the existing health ConfigMap. Without this, a pod restart re-initializes +// every component as unhealthy (see Loadcomponents), so the first health check +// looks like a transition and stamps time.Now() — resetting the "status held +// since" timestamp of components that never actually changed state. +// +// Only Status and Timestamp are seeded; the rest of each component (endpoints, +// workloads, namespace) always comes from the current config. Entries in the +// ConfigMap that no longer match a configured component are ignored, and a +// missing ConfigMap (first-ever run) is not an error. +func (m *Monitor) seedFromConfigMap(ctx context.Context, namespace, name string) error { + if len(m.components) == 0 { + return nil // nothing to restore + } + + var cm corev1.ConfigMap + nn := types.NamespacedName{Namespace: namespace, Name: name} + if err := m.k8sClient.Get(ctx, nn, &cm); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("read health configmap %s/%s: %w", namespace, name, err) + } + + for i := range m.components { + raw, ok := cm.Data[m.components[i].ComponentName] + if !ok { + continue + } + var entry statusEntry + if err := yaml.Unmarshal([]byte(raw), &entry); err != nil { + // A malformed entry just means we fall back to the fresh default for + // this component; log via caller context, don't fail the whole seed. + continue + } + if entry.Status != healthStatusHealthy && entry.Status != healthStatusUnhealthy { + continue + } + m.components[i].Status = entry.Status + m.components[i].Timestamp = entry.Timestamp + } + + return nil +} + +func (m *Monitor) RunHealthChecks(ctx context.Context) { + logger := zerolog.Ctx(ctx) + ticker := time.NewTicker(healthCheckInterval) + defer ticker.Stop() + cmNamespace := configMapNamespace() + hbPath := heartbeatPath() + + // Restore last-known statuses so a restart doesn't reset the timestamp of + // components whose health hasn't actually changed. Best-effort: on failure + // we keep the fresh defaults and log. + if err := m.seedFromConfigMap(ctx, cmNamespace, controlPlaneHealthCM); err != nil { + logger.Error().Err(err).Msg("failed to seed health status from configmap") + } + + // Beat once on entry so the liveness probe passes during the first interval, + // before any tick has fired. + if err := writeHeartbeat(hbPath); err != nil { + logger.Error().Err(err).Msg("failed to write initial heartbeat") + } + + var ( + wg sync.WaitGroup + err error + ) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + for i := range m.components { + wg.Go(func() { + m.checkComponentHealth(ctx, i) + }) + } + wg.Wait() + + if err = m.saveStatusToConfigMap(ctx, cmNamespace, controlPlaneHealthCM); err != nil { + logger.Error().Err(err).Msg("failed to save health status to configmap") + } + + // A completed cycle is the monitor's proof of life: refresh the + // heartbeat the liveness probe reads. We beat even when the + // ConfigMap write failed — the loop is still ticking, which is what + // liveness measures; the persist error is logged separately. + if err = writeHeartbeat(hbPath); err != nil { + logger.Error().Err(err).Msg("failed to write heartbeat") + } + } + } +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/health_test.go b/src/uis/nvcf-ui/backend/internal/control-plane/health_test.go new file mode 100644 index 000000000..0a286d86a --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/health_test.go @@ -0,0 +1,625 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "context" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "go.yaml.in/yaml/v3" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestLoadcomponents(t *testing.T) { + t.Parallel() + + const validYAML = ` +components: + - name: web + namespace: nvcf + endpoints: + - example.com + - api.example.com:8080 + - name: db + namespace: default + k8s_workloads: + - name: postgres + type: Deployment +` + + tests := []struct { + name string + yaml string + noFile bool + wantErr bool + check func(t *testing.T, m *Monitor) + }{ + { + name: "valid http and k8s components", + yaml: validYAML, + check: func(t *testing.T, m *Monitor) { + if len(m.components) != 2 { + t.Fatalf("got %d components, want 2", len(m.components)) + } + http := m.components[0] + if http.ComponentName != "web" || http.Namespace != "nvcf" || http.Status != healthStatusUnhealthy { + t.Errorf("http component = %+v", http) + } + // Timestamp is seeded at load so a never-transitioning unhealthy + // component reports a real time, not the zero value. + if http.Timestamp.IsZero() { + t.Errorf("http component Timestamp not seeded: got zero value") + } + if len(http.endpoints) != 2 || http.endpoints[0] != "http://example.com" { + t.Errorf("endpoints not normalized with scheme: %v", http.endpoints) + } + k8s := m.components[1] + if k8s.Namespace != "default" { + t.Errorf("k8s component namespace = %q, want default", k8s.Namespace) + } + if len(k8s.workloads) != 1 { + t.Fatalf("got %d workloads, want 1", len(k8s.workloads)) + } + if _, ok := k8s.workloads[0].obj.(*deploymentWorkload); !ok { + t.Errorf("workload obj = %T, want *deploymentWorkload", k8s.workloads[0].obj) + } + }, + }, + {name: "missing file", noFile: true, wantErr: true}, + {name: "invalid yaml", yaml: "components: [::", wantErr: true}, + { + name: "component missing name", + yaml: "components:\n - endpoints: [example.com]\n", + wantErr: true, + }, + { + name: "component missing namespace", + yaml: "components:\n - name: web\n endpoints: [example.com]\n", + wantErr: true, + }, + { + name: "component with neither endpoints nor workloads", + yaml: "components:\n - name: web\n namespace: nvcf\n", + wantErr: true, + }, + { + name: "component with both endpoints and workloads", + yaml: "components:\n - name: web\n namespace: nvcf\n endpoints: [example.com]\n k8s_workloads:\n - name: pg\n type: deployment\n", + check: func(t *testing.T, m *Monitor) { + if len(m.components) != 1 { + t.Fatalf("got %d components, want 1", len(m.components)) + } + c := m.components[0] + if len(c.endpoints) != 1 || len(c.workloads) != 1 { + t.Errorf("want both endpoints and workloads populated, got endpoints=%v workloads=%d", c.endpoints, len(c.workloads)) + } + }, + }, + { + name: "endpoint with scheme rejected", + yaml: "components:\n - name: web\n namespace: nvcf\n endpoints: [http://example.com]\n", + wantErr: true, + }, + { + name: "empty endpoint rejected", + yaml: "components:\n - name: web\n namespace: nvcf\n endpoints: ['']\n", + wantErr: true, + }, + { + name: "missing workload name", + yaml: "components:\n - name: db\n namespace: default\n k8s_workloads:\n - type: deployment\n", + wantErr: true, + }, + { + name: "invalid workload type", + yaml: "components:\n - name: db\n namespace: default\n k8s_workloads:\n - name: pg\n type: cronjob\n", + wantErr: true, + }, + { + name: "all workload types map to their wrappers", + yaml: ` +components: + - name: mixed + namespace: default + k8s_workloads: + - {name: dep, type: deployment} + - {name: sts, type: statefulset} + - {name: ds, type: daemonset} + - {name: rs, type: replicaset} +`, + check: func(t *testing.T, m *Monitor) { + ws := m.components[0].workloads + if len(ws) != 4 { + t.Fatalf("got %d workloads, want 4", len(ws)) + } + if _, ok := ws[0].obj.(*deploymentWorkload); !ok { + t.Errorf("ws[0] obj = %T, want *deploymentWorkload", ws[0].obj) + } + if _, ok := ws[1].obj.(*statefulSetWorkload); !ok { + t.Errorf("ws[1] obj = %T, want *statefulSetWorkload", ws[1].obj) + } + if _, ok := ws[2].obj.(*daemonSetWorkload); !ok { + t.Errorf("ws[2] obj = %T, want *daemonSetWorkload", ws[2].obj) + } + if _, ok := ws[3].obj.(*replicaSetWorkload); !ok { + t.Errorf("ws[3] obj = %T, want *replicaSetWorkload", ws[3].obj) + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "components.yaml") + if !tt.noFile { + if err := os.WriteFile(path, []byte(tt.yaml), 0o600); err != nil { + t.Fatalf("write temp file: %v", err) + } + } + + m := &Monitor{} + err := m.Loadcomponents(path) + if (err != nil) != tt.wantErr { + t.Fatalf("Loadcomponents err = %v, wantErr %v", err, tt.wantErr) + } + if err == nil && tt.check != nil { + tt.check(t, m) + } + }) + } +} + +func TestCheckComponentHealthHTTP(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + statusCode int + closed bool // serve from an already-closed server -> unreachable + malformed bool // unparseable endpoint -> request cannot be built + want healthStatus + }{ + {name: "2xx is healthy", statusCode: http.StatusOK, want: healthStatusHealthy}, + {name: "5xx is unhealthy", statusCode: http.StatusInternalServerError, want: healthStatusUnhealthy}, + {name: "unreachable is unhealthy", closed: true, want: healthStatusUnhealthy}, + {name: "malformed endpoint is unhealthy", malformed: true, want: healthStatusUnhealthy}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + endpoint := "://" // unparseable; only used by the malformed case + if !tt.malformed { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(tt.statusCode) + })) + if tt.closed { + srv.Close() + } else { + t.Cleanup(srv.Close) + } + endpoint = srv.URL + } + + m := &Monitor{ + httpClient: &http.Client{Timeout: 2 * time.Second}, + components: []componentHealth{ + {ComponentName: "c", Status: healthStatusUnhealthy, endpoints: []string{endpoint}}, + }, + } + + m.checkComponentHealth(context.Background(), 0) + + if got := m.components[0].Status; got != tt.want { + t.Errorf("status = %q, want %q", got, tt.want) + } + }) + } +} + +func TestCheckComponentHealthWorkloads(t *testing.T) { + t.Parallel() + + const ns, name = "default", "web" + selLabels := map[string]string{"app": "web"} + + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + + readyDeploy := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: name}, + Spec: appsv1.DeploymentSpec{Replicas: ptr[int32](1), Selector: &metav1.LabelSelector{MatchLabels: selLabels}}, + Status: appsv1.DeploymentStatus{Replicas: 1, ReadyReplicas: 1}, + } + readyPod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: "web-1", Labels: selLabels}, + Status: corev1.PodStatus{Phase: corev1.PodRunning, ContainerStatuses: []corev1.ContainerStatus{{Name: "c", Ready: true}}}, + } + + tests := []struct { + name string + objects []client.Object // seeded cluster state; nil => deployment absent + initial healthStatus + want healthStatus + }{ + // drives the workloads branch and a healthy transition. + {name: "healthy workload becomes healthy", objects: []client.Object{readyDeploy, readyPod}, initial: healthStatusUnhealthy, want: healthStatusHealthy}, + // drives the workloads branch and the unhealthy-transition error log. + {name: "failing workload becomes unhealthy", objects: nil, initial: healthStatusHealthy, want: healthStatusUnhealthy}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.objects...).Build() + m := &Monitor{ + k8sClient: cl, + components: []componentHealth{{ + ComponentName: "c", + Namespace: ns, + Status: tt.initial, + workloads: []k8sWorkload{{Name: name, Type: "deployment", obj: &deploymentWorkload{}}}, + }}, + } + + m.checkComponentHealth(context.Background(), 0) + + if got := m.components[0].Status; got != tt.want { + t.Errorf("status = %q, want %q", got, tt.want) + } + }) + } +} + +// TestCheckComponentHealthBoth covers a component that defines both endpoints +// and workloads: it is healthy only when both checks pass. +func TestCheckComponentHealthBoth(t *testing.T) { + t.Parallel() + + const ns, name = "default", "web" + selLabels := map[string]string{"app": "web"} + + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + readyDeploy := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: name}, + Spec: appsv1.DeploymentSpec{Replicas: ptr[int32](1), Selector: &metav1.LabelSelector{MatchLabels: selLabels}}, + Status: appsv1.DeploymentStatus{Replicas: 1, ReadyReplicas: 1}, + } + readyPod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: "web-1", Labels: selLabels}, + Status: corev1.PodStatus{Phase: corev1.PodRunning, ContainerStatuses: []corev1.ContainerStatus{{Name: "c", Ready: true}}}, + } + + tests := []struct { + name string + endpointStatus int + workloadReady bool + want healthStatus + }{ + {name: "both healthy", endpointStatus: http.StatusOK, workloadReady: true, want: healthStatusHealthy}, + {name: "endpoint healthy but workload missing", endpointStatus: http.StatusOK, workloadReady: false, want: healthStatusUnhealthy}, + {name: "workload healthy but endpoint failing", endpointStatus: http.StatusInternalServerError, workloadReady: true, want: healthStatusUnhealthy}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(tt.endpointStatus) + })) + t.Cleanup(srv.Close) + + builder := fake.NewClientBuilder().WithScheme(scheme) + if tt.workloadReady { + builder = builder.WithObjects(readyDeploy, readyPod) + } + + m := &Monitor{ + k8sClient: builder.Build(), + httpClient: &http.Client{Timeout: 2 * time.Second}, + components: []componentHealth{{ + ComponentName: "c", + Namespace: ns, + Status: healthStatusUnhealthy, + endpoints: []string{srv.URL}, + workloads: []k8sWorkload{{Name: name, Type: "deployment", obj: &deploymentWorkload{}}}, + }}, + } + + m.checkComponentHealth(context.Background(), 0) + + if got := m.components[0].Status; got != tt.want { + t.Errorf("status = %q, want %q", got, tt.want) + } + }) + } +} + +func TestSaveStatusToConfigMap(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + + tests := []struct { + name string + components []componentHealth + wantKeys []string + }{ + { + name: "no components writes empty configmap", + wantKeys: nil, + }, + { + name: "multiple components keyed by name", + components: []componentHealth{ + {ComponentName: "a", Namespace: "ns-a", Status: healthStatusHealthy, Timestamp: time.Unix(0, 0).UTC()}, + {ComponentName: "b", Namespace: "ns-b", Status: healthStatusUnhealthy}, + }, + wantKeys: []string{"a", "b"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cl := fake.NewClientBuilder().WithScheme(scheme).Build() + m := &Monitor{k8sClient: cl, components: tt.components} + + if err := m.saveStatusToConfigMap(context.Background(), "default", "status"); err != nil { + t.Fatalf("saveStatusToConfigMap: %v", err) + } + + var cm corev1.ConfigMap + key := client.ObjectKey{Namespace: "default", Name: "status"} + if err := cl.Get(context.Background(), key, &cm); err != nil { + t.Fatalf("get configmap: %v", err) + } + + if len(cm.Data) != len(tt.wantKeys) { + t.Fatalf("configmap has %d keys, want %d (%v)", len(cm.Data), len(tt.wantKeys), cm.Data) + } + for _, k := range tt.wantKeys { + if _, ok := cm.Data[k]; !ok { + t.Errorf("missing key %q in configmap data", k) + } + } + // status value is YAML-marshalled; confirm it round-trips the status and namespace. + for _, c := range tt.components { + if !strings.Contains(cm.Data[c.ComponentName], string(c.Status)) { + t.Errorf("data[%q] = %q, want it to contain %q", c.ComponentName, cm.Data[c.ComponentName], c.Status) + } + if !strings.Contains(cm.Data[c.ComponentName], c.Namespace) { + t.Errorf("data[%q] = %q, want it to contain namespace %q", c.ComponentName, cm.Data[c.ComponentName], c.Namespace) + } + } + }) + } +} + +func TestSeedFromConfigMap(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + + // healthySince is when the component last transitioned; seeding must preserve + // it rather than resetting to the restart time. + healthySince := time.Unix(1_600_000_000, 0).UTC() + + marshalEntry := func(t *testing.T, e statusEntry) string { + t.Helper() + out, err := yaml.Marshal(e) + if err != nil { + t.Fatalf("marshal entry: %v", err) + } + return string(out) + } + + t.Run("restores status and timestamp for matching components", func(t *testing.T) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: controlPlaneHealthCM}, + Data: map[string]string{ + "a": marshalEntry(t, statusEntry{Namespace: "ns-a", Status: healthStatusHealthy, Timestamp: healthySince}), + // stale entry for a component no longer configured — must be ignored. + "gone": marshalEntry(t, statusEntry{Namespace: "ns-x", Status: healthStatusHealthy, Timestamp: healthySince}), + }, + } + cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cm).Build() + + // Fresh post-restart state: everything unhealthy, stamped "now". + now := time.Now().UTC() + m := &Monitor{ + k8sClient: cl, + components: []componentHealth{ + {ComponentName: "a", Namespace: "ns-a", Status: healthStatusUnhealthy, Timestamp: now}, + {ComponentName: "b", Namespace: "ns-b", Status: healthStatusUnhealthy, Timestamp: now}, + }, + } + + if err := m.seedFromConfigMap(context.Background(), "default", controlPlaneHealthCM); err != nil { + t.Fatalf("seedFromConfigMap: %v", err) + } + + if m.components[0].Status != healthStatusHealthy { + t.Errorf("component a status = %q, want %q", m.components[0].Status, healthStatusHealthy) + } + if !m.components[0].Timestamp.Equal(healthySince) { + t.Errorf("component a timestamp = %v, want restored %v", m.components[0].Timestamp, healthySince) + } + // b has no ConfigMap entry — it keeps its fresh default. + if m.components[1].Status != healthStatusUnhealthy || !m.components[1].Timestamp.Equal(now) { + t.Errorf("component b = %+v, want unchanged default", m.components[1]) + } + }) + + t.Run("missing configmap is not an error", func(t *testing.T) { + cl := fake.NewClientBuilder().WithScheme(scheme).Build() + now := time.Now().UTC() + m := &Monitor{ + k8sClient: cl, + components: []componentHealth{ + {ComponentName: "a", Status: healthStatusUnhealthy, Timestamp: now}, + }, + } + + if err := m.seedFromConfigMap(context.Background(), "default", controlPlaneHealthCM); err != nil { + t.Fatalf("seedFromConfigMap on missing configmap: %v", err) + } + if m.components[0].Status != healthStatusUnhealthy || !m.components[0].Timestamp.Equal(now) { + t.Errorf("component a = %+v, want unchanged default", m.components[0]) + } + }) + + t.Run("malformed entry falls back to default", func(t *testing.T) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: controlPlaneHealthCM}, + Data: map[string]string{"a": "not: [valid: yaml"}, + } + cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cm).Build() + now := time.Now().UTC() + m := &Monitor{ + k8sClient: cl, + components: []componentHealth{ + {ComponentName: "a", Status: healthStatusUnhealthy, Timestamp: now}, + }, + } + + if err := m.seedFromConfigMap(context.Background(), "default", controlPlaneHealthCM); err != nil { + t.Fatalf("seedFromConfigMap: %v", err) + } + if m.components[0].Status != healthStatusUnhealthy || !m.components[0].Timestamp.Equal(now) { + t.Errorf("component a = %+v, want unchanged default after malformed entry", m.components[0]) + } + }) +} + +func TestNew(t *testing.T) { + t.Parallel() + + cl := fake.NewClientBuilder().Build() + m := New(cl) + + if m == nil { + t.Fatal("New returned nil") + } + if m.k8sClient != cl { + t.Error("k8sClient is not the provided client") + } + if m.httpClient == nil { + t.Fatal("httpClient is nil") + } + if m.httpClient.Timeout != 20*time.Second { + t.Errorf("httpClient.Timeout = %v, want 20s", m.httpClient.Timeout) + } + if len(m.components) != 0 { + t.Errorf("components = %d, want 0 before Loadcomponents", len(m.components)) + } +} + +func TestRunHealthChecks(t *testing.T) { + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + + tests := []struct { + name string + statusCode int + want healthStatus + }{ + {name: "healthy endpoint is persisted healthy", statusCode: http.StatusOK, want: healthStatusHealthy}, + {name: "failing endpoint is persisted unhealthy", statusCode: http.StatusInternalServerError, want: healthStatusUnhealthy}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(tt.statusCode) + })) + t.Cleanup(srv.Close) + + // Shorten the otherwise 20s cycle so the loop ticks promptly. + orig := healthCheckInterval + healthCheckInterval = time.Millisecond + t.Cleanup(func() { healthCheckInterval = orig }) + + cl := fake.NewClientBuilder().WithScheme(scheme).Build() + m := &Monitor{ + k8sClient: cl, + httpClient: &http.Client{Timeout: 2 * time.Second}, + components: []componentHealth{ + {ComponentName: "web", Status: healthStatusUnhealthy, endpoints: []string{srv.URL}}, + }, + } + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + go func() { + m.RunHealthChecks(ctx) + close(done) + }() + + got := pollPersistedStatus(t, cl, "web", tt.want) + cancel() + + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("RunHealthChecks did not return after context cancel") + } + + if got != tt.want { + t.Errorf("persisted status = %q, want %q", got, tt.want) + } + }) + } +} + +// pollPersistedStatus waits for RunHealthChecks to write want into the status +// ConfigMap for the named component, returning the observed status (or failing). +func pollPersistedStatus(t *testing.T, cl client.Client, component string, want healthStatus) healthStatus { + t.Helper() + for range 200 { + var cm corev1.ConfigMap + if err := cl.Get(context.Background(), client.ObjectKey{Namespace: defaultNamespace, Name: controlPlaneHealthCM}, &cm); err == nil { + var se statusEntry + if err := yaml.Unmarshal([]byte(cm.Data[component]), &se); err == nil && se.Status == want { + return se.Status + } + } + time.Sleep(10 * time.Millisecond) + } + t.Fatalf("status %q for %q not persisted within timeout", want, component) + return "" +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/heartbeat.go b/src/uis/nvcf-ui/backend/internal/control-plane/heartbeat.go new file mode 100644 index 000000000..d6c4437b5 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/heartbeat.go @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/utils" +) + +const ( + heartbeatFileEnvVar = "HEARTBEAT_FILE" + defaultHeartbeatFile = "/tmp/backend-heartbeat" +) + +// livenessMaxAge is how stale the heartbeat may be before the monitor is +// considered hung. The loop refreshes the heartbeat once per +// healthCheckInterval, so we tolerate three missed cycles — covering a slow +// run of sequential endpoint probes — before declaring the process dead. +func livenessMaxAge() time.Duration { + return 3 * healthCheckInterval +} + +// heartbeatPath resolves the heartbeat file location from heartbeatFileEnvVar +// or defaultHeartbeatFile. +func heartbeatPath() string { + return utils.GetEnvOr(heartbeatFileEnvVar, defaultHeartbeatFile) +} + +// writeHeartbeat records the current time in the heartbeat file. It writes to a +// sibling temp file and renames it into place so a probe reading concurrently +// never observes a partial write. +func writeHeartbeat(path string) error { + tmp, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".tmp-*") + if err != nil { + return fmt.Errorf("create heartbeat temp file: %w", err) + } + tmpName := tmp.Name() + defer func() { _ = os.Remove(tmpName) }() // no-op once renamed + + if _, err := tmp.WriteString(time.Now().UTC().Format(time.RFC3339Nano)); err != nil { + _ = tmp.Close() + return fmt.Errorf("write heartbeat: %w", err) + } + if err := tmp.Close(); err != nil { + return fmt.Errorf("close heartbeat temp file: %w", err) + } + if err := os.Rename(tmpName, path); err != nil { + return fmt.Errorf("rename heartbeat into place: %w", err) + } + return nil +} + +// CheckLiveness reports whether the monitor's heartbeat file exists and was +// refreshed within livenessMaxAge. It is the body of the exec liveness probe: +// the kubelet re-execs this binary in liveness mode, so the check reuses the +// same path resolution and staleness threshold as the loop that writes the +// file. A missing, unparseable, or stale heartbeat returns a non-nil error, +// which the caller turns into a non-zero exit so the kubelet restarts the pod. +func CheckLiveness() error { + path := heartbeatPath() + maxAge := livenessMaxAge() + + raw, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("read heartbeat %s: %w", path, err) + } + beat, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(string(raw))) + if err != nil { + return fmt.Errorf("parse heartbeat %q: %w", strings.TrimSpace(string(raw)), err) + } + if age := time.Since(beat); age > maxAge { + return fmt.Errorf("heartbeat is stale: last beat %s ago, max %s", age.Round(time.Second), maxAge) + } + return nil +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/heartbeat_test.go b/src/uis/nvcf-ui/backend/internal/control-plane/heartbeat_test.go new file mode 100644 index 000000000..23d2fa94a --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/heartbeat_test.go @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" +) + +func TestWriteHeartbeatThenCheckLivenessFresh(t *testing.T) { + path := filepath.Join(t.TempDir(), "heartbeat") + t.Setenv(heartbeatFileEnvVar, path) + + if err := writeHeartbeat(path); err != nil { + t.Fatalf("writeHeartbeat: %v", err) + } + if err := CheckLiveness(); err != nil { + t.Errorf("CheckLiveness on a fresh heartbeat: %v", err) + } +} + +func TestWriteHeartbeatIsAtomicOverwrite(t *testing.T) { + path := filepath.Join(t.TempDir(), "heartbeat") + + if err := writeHeartbeat(path); err != nil { + t.Fatalf("first writeHeartbeat: %v", err) + } + if err := writeHeartbeat(path); err != nil { + t.Fatalf("second writeHeartbeat: %v", err) + } + + // The rename leaves exactly one file (the heartbeat) and no temp leftovers. + entries, err := os.ReadDir(filepath.Dir(path)) + if err != nil { + t.Fatalf("read dir: %v", err) + } + if len(entries) != 1 || entries[0].Name() != filepath.Base(path) { + names := make([]string, len(entries)) + for i, e := range entries { + names[i] = e.Name() + } + t.Errorf("dir contents = %v, want only %q", names, filepath.Base(path)) + } +} + +func TestCheckLivenessMissingFile(t *testing.T) { + t.Setenv(heartbeatFileEnvVar, filepath.Join(t.TempDir(), "does-not-exist")) + + if err := CheckLiveness(); err == nil { + t.Error("CheckLiveness on a missing heartbeat returned nil, want error") + } +} + +func TestCheckLivenessUnparseable(t *testing.T) { + path := filepath.Join(t.TempDir(), "heartbeat") + t.Setenv(heartbeatFileEnvVar, path) + if err := os.WriteFile(path, []byte("not-a-timestamp"), 0o600); err != nil { + t.Fatalf("write file: %v", err) + } + + if err := CheckLiveness(); err == nil { + t.Error("CheckLiveness on an unparseable heartbeat returned nil, want error") + } +} + +func TestCheckLivenessStale(t *testing.T) { + path := filepath.Join(t.TempDir(), "heartbeat") + t.Setenv(heartbeatFileEnvVar, path) + + // A beat older than livenessMaxAge must read as stale. + stale := time.Now().UTC().Add(-livenessMaxAge() - time.Minute).Format(time.RFC3339Nano) + if err := os.WriteFile(path, []byte(stale), 0o600); err != nil { + t.Fatalf("write file: %v", err) + } + + if err := CheckLiveness(); err == nil { + t.Error("CheckLiveness on a stale heartbeat returned nil, want error") + } +} + +func TestRunHealthChecksWritesHeartbeat(t *testing.T) { + path := filepath.Join(t.TempDir(), "heartbeat") + t.Setenv(heartbeatFileEnvVar, path) + + // The monitor beats once on entry, before any tick fires. Keep the interval + // long so the test observes that initial beat rather than a cycle beat. + orig := healthCheckInterval + healthCheckInterval = time.Hour + t.Cleanup(func() { healthCheckInterval = orig }) + + m := &Monitor{} + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + go func() { + m.RunHealthChecks(ctx) + close(done) + }() + + var ok bool + for range 200 { + if _, err := os.Stat(path); err == nil { + ok = true + break + } + time.Sleep(5 * time.Millisecond) + } + cancel() + <-done + + if !ok { + t.Error("RunHealthChecks did not write the heartbeat file on entry") + } +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/httphealth.go b/src/uis/nvcf-ui/backend/internal/control-plane/httphealth.go new file mode 100644 index 000000000..508ac7ff8 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/httphealth.go @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" +) + +func validateFQDN(fqdn string) error { + if strings.HasPrefix(fqdn, "http://") || strings.HasPrefix(fqdn, "https://") { + return fmt.Errorf("fqdn %q must not include a scheme", fqdn) + } + u, err := url.Parse("http://" + fqdn) + if err != nil || u.Host == "" { + return fmt.Errorf("invalid fqdn %q", fqdn) + } + return nil +} + +// checkEndpoints returns nil if every endpoint returns a 2xx response, +// otherwise an error describing the first failure. +func (m *Monitor) checkEndpoints(ctx context.Context, index int) error { + for _, ep := range m.components[index].endpoints { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, ep, nil) + if err != nil { + return fmt.Errorf("create request for %s: %w", ep, err) + } + resp, err := m.httpClient.Do(req) + if err != nil { + return fmt.Errorf("request %s: %w", ep, err) + } + _ = resp.Body.Close() + + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + return fmt.Errorf("endpoint %s returned status %d", ep, resp.StatusCode) + } + } + return nil +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/k8shealth.go b/src/uis/nvcf-ui/backend/internal/control-plane/k8shealth.go new file mode 100644 index 000000000..30d298ebf --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/k8shealth.go @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "context" + "fmt" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var validK8sWorkloadTypes = map[string]bool{ + "deployment": true, + "statefulset": true, + "daemonset": true, + "replicaset": true, +} + +// workloadObject is a health-checkable k8s workload. object() returns the +// concrete, scheme-registered API object to fetch into (the wrapper holds it by +// value rather than embedding it, so the client resolves the real kind's GVK); +// replicasReady and podSelector then read from that fetched object. +type workloadObject interface { + object() client.Object + replicasReady() error + podSelector() (labels.Selector, error) +} + +type deploymentWorkload struct{ d appsv1.Deployment } + +func (w *deploymentWorkload) object() client.Object { return &w.d } +func (w *deploymentWorkload) replicasReady() error { + if w.d.Status.ReadyReplicas != ptrVal(w.d.Spec.Replicas) || w.d.Status.Replicas != w.d.Status.ReadyReplicas { + return fmt.Errorf("replicas not ready: desired=%d ready=%d", ptrVal(w.d.Spec.Replicas), w.d.Status.ReadyReplicas) + } + return nil +} +func (w *deploymentWorkload) podSelector() (labels.Selector, error) { + return metav1.LabelSelectorAsSelector(w.d.Spec.Selector) +} + +type statefulSetWorkload struct{ s appsv1.StatefulSet } + +func (w *statefulSetWorkload) object() client.Object { return &w.s } +func (w *statefulSetWorkload) replicasReady() error { + if w.s.Status.ReadyReplicas != ptrVal(w.s.Spec.Replicas) || w.s.Status.Replicas != w.s.Status.ReadyReplicas { + return fmt.Errorf("replicas not ready: desired=%d ready=%d", ptrVal(w.s.Spec.Replicas), w.s.Status.ReadyReplicas) + } + return nil +} +func (w *statefulSetWorkload) podSelector() (labels.Selector, error) { + return metav1.LabelSelectorAsSelector(w.s.Spec.Selector) +} + +type daemonSetWorkload struct{ d appsv1.DaemonSet } + +func (w *daemonSetWorkload) object() client.Object { return &w.d } +func (w *daemonSetWorkload) replicasReady() error { + if w.d.Status.NumberReady != w.d.Status.DesiredNumberScheduled { + return fmt.Errorf("replicas not ready: desired=%d ready=%d", w.d.Status.DesiredNumberScheduled, w.d.Status.NumberReady) + } + return nil +} +func (w *daemonSetWorkload) podSelector() (labels.Selector, error) { + return metav1.LabelSelectorAsSelector(w.d.Spec.Selector) +} + +type replicaSetWorkload struct{ r appsv1.ReplicaSet } + +func (w *replicaSetWorkload) object() client.Object { return &w.r } +func (w *replicaSetWorkload) replicasReady() error { + if w.r.Status.ReadyReplicas != ptrVal(w.r.Spec.Replicas) || w.r.Status.Replicas != w.r.Status.ReadyReplicas { + return fmt.Errorf("replicas not ready: desired=%d ready=%d", ptrVal(w.r.Spec.Replicas), w.r.Status.ReadyReplicas) + } + return nil +} +func (w *replicaSetWorkload) podSelector() (labels.Selector, error) { + return metav1.LabelSelectorAsSelector(w.r.Spec.Selector) +} + +func ptrVal(p *int32) int32 { + if p == nil { + return 1 + } + return *p +} + +// workloadSelector fetches the workload, checks replicas, and returns the pod label selector. +func (m *Monitor) workloadSelector(ctx context.Context, namespace string, w k8sWorkload) (labels.Selector, error) { + nn := types.NamespacedName{Namespace: namespace, Name: w.Name} + if err := m.k8sClient.Get(ctx, nn, w.obj.object()); err != nil { + return nil, err + } + if err := w.obj.replicasReady(); err != nil { + return nil, err + } + return w.obj.podSelector() +} + +// checkWorkloads returns nil if every workload's pods are running and ready, +// otherwise an error describing the first problem found. All workloads are +// looked up in the component's namespace. +func (m *Monitor) checkWorkloads(ctx context.Context, index int) error { + namespace := m.components[index].Namespace + for _, w := range m.components[index].workloads { + sel, err := m.workloadSelector(ctx, namespace, w) + if err != nil { + return fmt.Errorf("workload %s/%s: %w", namespace, w.Name, err) + } + + var podList corev1.PodList + if err := m.k8sClient.List(ctx, &podList, &client.ListOptions{ + Namespace: namespace, + LabelSelector: sel, + }); err != nil { + return fmt.Errorf("list pods for %s/%s: %w", namespace, w.Name, err) + } + + for _, pod := range podList.Items { + if pod.Status.Phase != corev1.PodRunning { + return fmt.Errorf("pod %s is in phase %s", pod.Name, pod.Status.Phase) + } + for _, cs := range pod.Status.ContainerStatuses { + if !cs.Ready { + return fmt.Errorf("pod %s container %s is not ready", pod.Name, cs.Name) + } + } + } + } + return nil +} diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/k8shealth_test.go b/src/uis/nvcf-ui/backend/internal/control-plane/k8shealth_test.go new file mode 100644 index 000000000..24449c27f --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/control-plane/k8shealth_test.go @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlplane + +import ( + "context" + "fmt" + "testing" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" +) + +func ptr[T any](v T) *T { return &v } + +func TestReplicasReady(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + obj workloadObject + wantErr bool + }{ + { + name: "deployment ready", + obj: &deploymentWorkload{appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: ptr[int32](3)}, Status: appsv1.DeploymentStatus{Replicas: 3, ReadyReplicas: 3}}}, + wantErr: false, + }, + { + name: "deployment not ready", + obj: &deploymentWorkload{appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: ptr[int32](3)}, Status: appsv1.DeploymentStatus{Replicas: 3, ReadyReplicas: 2}}}, + wantErr: true, + }, + { + name: "deployment nil replicas defaults to one", + obj: &deploymentWorkload{appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: nil}, Status: appsv1.DeploymentStatus{Replicas: 1, ReadyReplicas: 1}}}, + wantErr: false, + }, + { + name: "statefulset ready", + obj: &statefulSetWorkload{appsv1.StatefulSet{Spec: appsv1.StatefulSetSpec{Replicas: ptr[int32](2)}, Status: appsv1.StatefulSetStatus{Replicas: 2, ReadyReplicas: 2}}}, + wantErr: false, + }, + { + name: "statefulset not ready", + obj: &statefulSetWorkload{appsv1.StatefulSet{Spec: appsv1.StatefulSetSpec{Replicas: ptr[int32](2)}, Status: appsv1.StatefulSetStatus{Replicas: 1, ReadyReplicas: 1}}}, + wantErr: true, + }, + { + name: "daemonset ready", + obj: &daemonSetWorkload{appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{DesiredNumberScheduled: 4, NumberReady: 4}}}, + wantErr: false, + }, + { + name: "daemonset not ready", + obj: &daemonSetWorkload{appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{DesiredNumberScheduled: 4, NumberReady: 3}}}, + wantErr: true, + }, + { + name: "replicaset ready", + obj: &replicaSetWorkload{appsv1.ReplicaSet{Spec: appsv1.ReplicaSetSpec{Replicas: ptr[int32](1)}, Status: appsv1.ReplicaSetStatus{Replicas: 1, ReadyReplicas: 1}}}, + wantErr: false, + }, + { + name: "replicaset not ready", + obj: &replicaSetWorkload{appsv1.ReplicaSet{Spec: appsv1.ReplicaSetSpec{Replicas: ptr[int32](1)}, Status: appsv1.ReplicaSetStatus{Replicas: 1, ReadyReplicas: 0}}}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.obj.replicasReady(); (err != nil) != tt.wantErr { + t.Errorf("replicasReady() err = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestWorkloadAccessors(t *testing.T) { + t.Parallel() + + sel := &metav1.LabelSelector{MatchLabels: map[string]string{"app": "x"}} + + tests := []struct { + name string + obj workloadObject + }{ + {name: "deployment", obj: &deploymentWorkload{appsv1.Deployment{Spec: appsv1.DeploymentSpec{Selector: sel}}}}, + {name: "statefulset", obj: &statefulSetWorkload{appsv1.StatefulSet{Spec: appsv1.StatefulSetSpec{Selector: sel}}}}, + {name: "daemonset", obj: &daemonSetWorkload{appsv1.DaemonSet{Spec: appsv1.DaemonSetSpec{Selector: sel}}}}, + {name: "replicaset", obj: &replicaSetWorkload{appsv1.ReplicaSet{Spec: appsv1.ReplicaSetSpec{Selector: sel}}}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.obj.object() == nil { + t.Error("object() returned nil") + } + s, err := tt.obj.podSelector() + if err != nil { + t.Fatalf("podSelector() error: %v", err) + } + if got := s.String(); got != "app=x" { + t.Errorf("selector = %q, want app=x", got) + } + }) + } +} + +func TestCheckWorkloads(t *testing.T) { + t.Parallel() + + const ns, name = "default", "web" + selLabels := map[string]string{"app": "web"} + + deploy := func(replicas, ready int32) *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: name}, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr(replicas), + Selector: &metav1.LabelSelector{MatchLabels: selLabels}, + }, + Status: appsv1.DeploymentStatus{Replicas: replicas, ReadyReplicas: ready}, + } + } + pod := func(phase corev1.PodPhase, ready bool) *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: "web-abc", Labels: selLabels}, + Status: corev1.PodStatus{ + Phase: phase, + ContainerStatuses: []corev1.ContainerStatus{{Name: "c", Ready: ready}}, + }, + } + } + + tests := []struct { + name string + objects []client.Object // seeded into the fake cluster; nil => deployment absent + listErr bool // make the pod List call fail + wantErr bool + }{ + {name: "all healthy", objects: []client.Object{deploy(1, 1), pod(corev1.PodRunning, true)}, wantErr: false}, + {name: "deployment missing", objects: nil, wantErr: true}, + {name: "replicas not ready", objects: []client.Object{deploy(2, 1)}, wantErr: true}, + {name: "pod not running", objects: []client.Object{deploy(1, 1), pod(corev1.PodPending, true)}, wantErr: true}, + {name: "pod container not ready", objects: []client.Object{deploy(1, 1), pod(corev1.PodRunning, false)}, wantErr: true}, + {name: "list pods fails", objects: []client.Object{deploy(1, 1)}, listErr: true, wantErr: true}, + } + + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatalf("add scheme: %v", err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + builder := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.objects...) + if tt.listErr { + builder = builder.WithInterceptorFuncs(interceptor.Funcs{ + List: func(_ context.Context, _ client.WithWatch, _ client.ObjectList, _ ...client.ListOption) error { + return fmt.Errorf("list failed") + }, + }) + } + cl := builder.Build() + m := &Monitor{ + k8sClient: cl, + components: []componentHealth{{ + Namespace: ns, + workloads: []k8sWorkload{{Name: name, Type: "deployment", obj: &deploymentWorkload{}}}, + }}, + } + + if err := m.checkWorkloads(context.Background(), 0); (err != nil) != tt.wantErr { + t.Errorf("checkWorkloads() err = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel new file mode 100644 index 000000000..0505053b2 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "middleware", + srcs = ["middleware.go"], + importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/middleware", + visibility = ["//backend:__subpackages__"], + deps = [ + "@com_github_rs_zerolog//:zerolog", + "@com_github_rs_zerolog//hlog", + ], +) + +go_test( + name = "middleware_test", + srcs = ["middleware_test.go"], + embed = [":middleware"], + deps = [ + "@com_github_rs_zerolog//:zerolog", + "@com_github_rs_zerolog//hlog", + ], +) + + +filegroup( + name = "sources", + srcs = glob(["*.go", "*.md"], allow_empty = True), + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/backend/internal/middleware/middleware.go b/src/uis/nvcf-ui/backend/internal/middleware/middleware.go new file mode 100644 index 000000000..d8282aae2 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/middleware/middleware.go @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package middleware + +import ( + "net/http" + "runtime" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/hlog" +) + +// AllowReadMethods rejects any request whose method is not GET or HEAD with a +// 405, before it reaches the wrapped handler. The Allow header advertises the +// permitted methods as required by the HTTP spec. +func AllowReadMethods(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet && r.Method != http.MethodHead { + w.Header().Set("Allow", "GET, HEAD") + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + h.ServeHTTP(w, r) + }) +} + +func EntryAudit(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + hlog.FromRequest(r).Info().Msg("Entry Audit") + } + h.ServeHTTP(w, r) + }) +} + +func AddRequestId(h http.Handler) http.Handler { + withRequestID := hlog.RequestIDHandler("request_id", "X-Request-Id")(h) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + h.ServeHTTP(w, r) + return + } + withRequestID.ServeHTTP(w, r) + }) +} + +func PanicRecovery(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + stack := make([]byte, 4096) + stack = stack[:runtime.Stack(stack, false)] + hlog.FromRequest(r).Error(). + Interface(zerolog.ErrorFieldName, err). + Str("stack_trace", string(stack)). + Msg("Recovered from panic") + w.WriteHeader(http.StatusInternalServerError) + } + }() + h.ServeHTTP(w, r) + }) +} diff --git a/src/uis/nvcf-ui/backend/internal/middleware/middleware_test.go b/src/uis/nvcf-ui/backend/internal/middleware/middleware_test.go new file mode 100644 index 000000000..31e4a4442 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/middleware/middleware_test.go @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package middleware + +import ( + "bytes" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/hlog" +) + +// recordingHandler records whether it was invoked and writes a fixed response. +type recordingHandler struct { + called bool + status int + body string +} + +func (rh *recordingHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) { + rh.called = true + if rh.status != 0 { + w.WriteHeader(rh.status) + } + if rh.body != "" { + _, _ = w.Write([]byte(rh.body)) + } +} + +// withLogger injects a zerolog logger into the request context so hlog.FromRequest +// resolves to it, capturing emitted logs into buf. +func withLogger(buf *bytes.Buffer, h http.Handler) http.Handler { + logger := zerolog.New(buf) + return hlog.NewHandler(logger)(h) +} + +func TestAllowReadMethods(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + method string + wantCalled bool + wantStatus int + }{ + {name: "GET is allowed", method: http.MethodGet, wantCalled: true, wantStatus: http.StatusOK}, + {name: "HEAD is allowed", method: http.MethodHead, wantCalled: true, wantStatus: http.StatusOK}, + {name: "POST is rejected", method: http.MethodPost, wantCalled: false, wantStatus: http.StatusMethodNotAllowed}, + {name: "PUT is rejected", method: http.MethodPut, wantCalled: false, wantStatus: http.StatusMethodNotAllowed}, + {name: "DELETE is rejected", method: http.MethodDelete, wantCalled: false, wantStatus: http.StatusMethodNotAllowed}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + next := &recordingHandler{} + h := AllowReadMethods(next) + + rr := httptest.NewRecorder() + h.ServeHTTP(rr, httptest.NewRequest(tt.method, "/v1/foo", nil)) + + if next.called != tt.wantCalled { + t.Errorf("next handler called = %v, want %v", next.called, tt.wantCalled) + } + if rr.Code != tt.wantStatus { + t.Errorf("status = %d, want %d", rr.Code, tt.wantStatus) + } + if !tt.wantCalled { + if allow := rr.Header().Get("Allow"); allow != "GET, HEAD" { + t.Errorf("Allow header = %q, want %q", allow, "GET, HEAD") + } + } + }) + } +} + +func TestEntryAudit(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + method string + wantLog bool + }{ + {name: "GET is not audited", method: http.MethodGet, wantLog: false}, + {name: "POST is audited", method: http.MethodPost, wantLog: true}, + {name: "DELETE is audited", method: http.MethodDelete, wantLog: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + next := &recordingHandler{} + var buf bytes.Buffer + h := withLogger(&buf, EntryAudit(next)) + + rr := httptest.NewRecorder() + h.ServeHTTP(rr, httptest.NewRequest(tt.method, "/v1/foo", nil)) + + if !next.called { + t.Fatal("next handler was not called") + } + gotLog := strings.Contains(buf.String(), "Entry Audit") + if gotLog != tt.wantLog { + t.Errorf("audit logged = %v, want %v (log: %q)", gotLog, tt.wantLog, buf.String()) + } + }) + } +} + +func TestAddRequestId(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + method string + wantReqID bool + }{ + {name: "GET has no request id", method: http.MethodGet, wantReqID: false}, + {name: "POST gets a request id", method: http.MethodPost, wantReqID: true}, + {name: "PUT gets a request id", method: http.MethodPut, wantReqID: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + next := &recordingHandler{} + h := AddRequestId(next) + + rr := httptest.NewRecorder() + h.ServeHTTP(rr, httptest.NewRequest(tt.method, "/v1/foo", nil)) + + if !next.called { + t.Fatal("next handler was not called") + } + gotReqID := rr.Header().Get("X-Request-Id") != "" + if gotReqID != tt.wantReqID { + t.Errorf("X-Request-Id present = %v, want %v", gotReqID, tt.wantReqID) + } + }) + } +} + +func TestPanicRecovery(t *testing.T) { + t.Parallel() + + t.Run("recovers from panic and returns 500", func(t *testing.T) { + panicking := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + panic("boom") + }) + var buf bytes.Buffer + h := withLogger(&buf, PanicRecovery(panicking)) + + rr := httptest.NewRecorder() + h.ServeHTTP(rr, httptest.NewRequest(http.MethodPost, "/v1/foo", nil)) + + if rr.Code != http.StatusInternalServerError { + t.Errorf("status = %d, want %d", rr.Code, http.StatusInternalServerError) + } + log := buf.String() + if !strings.Contains(log, "Recovered from panic") { + t.Errorf("expected panic log, got %q", log) + } + if !strings.Contains(log, "stack_trace") { + t.Errorf("expected stack_trace field in log, got %q", log) + } + }) + + t.Run("passes through when no panic", func(t *testing.T) { + next := &recordingHandler{status: http.StatusAccepted, body: "ok"} + h := PanicRecovery(next) + + rr := httptest.NewRecorder() + h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/v1/foo", nil)) + + if !next.called { + t.Fatal("next handler was not called") + } + if rr.Code != http.StatusAccepted { + t.Errorf("status = %d, want %d", rr.Code, http.StatusAccepted) + } + if rr.Body.String() != "ok" { + t.Errorf("body = %q, want %q", rr.Body.String(), "ok") + } + }) +} diff --git a/src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel new file mode 100644 index 000000000..7eb8bbbb6 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "token-watcher", + srcs = ["watcher.go"], + importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/token-watcher", + visibility = ["//backend:__subpackages__"], + deps = [ + "//backend/internal/utils", + "@com_github_fsnotify_fsnotify//:fsnotify", + "@com_github_rs_zerolog//:zerolog", + ], +) + +go_test( + name = "token-watcher_test", + srcs = ["watcher_test.go"], + embed = [":token-watcher"], + deps = ["@com_github_rs_zerolog//:zerolog"], +) + + +filegroup( + name = "sources", + srcs = glob(["*.go", "*.md"], allow_empty = True), + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/backend/internal/token-watcher/watcher.go b/src/uis/nvcf-ui/backend/internal/token-watcher/watcher.go new file mode 100644 index 000000000..91df5aef2 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/token-watcher/watcher.go @@ -0,0 +1,236 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tokenwatcher + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/rs/zerolog" + + "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/utils" +) + +const ( + expiryWarning = 5 * time.Minute + tokensFilePath = "TOKENS_PATH" + defaultTokensFilePath = "/var/run/secrets/vault/tokens.json" +) + +type apiTokens struct { + NvcfApiToken string `json:"nvcfApiToken"` + NvctApiToken string `json:"nvctApiToken"` + SisApiToken string `json:"sisApiToken"` +} + +type tokenState struct { + value string + valid bool + cancel context.CancelFunc +} + +type Watcher struct { + mu sync.RWMutex + wg sync.WaitGroup + ctx context.Context + tokensPath string + nvcf tokenState + nvct tokenState + sis tokenState +} + +// Wait blocks until every goroutine the watcher started has exited. Call it +// after the watcher's context is cancelled to guarantee a clean shutdown. +func (w *Watcher) Wait() { + w.wg.Wait() +} + +func (w *Watcher) NVCFToken() (string, bool) { + w.mu.RLock() + defer w.mu.RUnlock() + return w.nvcf.value, w.nvcf.valid +} + +func (w *Watcher) NVCTToken() (string, bool) { + w.mu.RLock() + defer w.mu.RUnlock() + return w.nvct.value, w.nvct.valid +} + +func (w *Watcher) SISToken() (string, bool) { + w.mu.RLock() + defer w.mu.RUnlock() + return w.sis.value, w.sis.valid +} + +func jwtExpiry(token string) (time.Time, error) { + parts := strings.SplitN(token, ".", 3) + if len(parts) != 3 { + return time.Time{}, fmt.Errorf("invalid JWT format") + } + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return time.Time{}, err + } + var claims struct { + Exp int64 `json:"exp"` + } + if err := json.Unmarshal(payload, &claims); err != nil { + return time.Time{}, err + } + if claims.Exp == 0 { + return time.Time{}, fmt.Errorf("no exp claim in token") + } + return time.Unix(claims.Exp, 0), nil +} + +// startExpiryWatcher fires invalidate() 5 minutes before the token's exp claim. +// ctx is per-token — cancel it to garbage-collect this goroutine when the token rotates. +func (w *Watcher) startExpiryWatcher(ctx context.Context, name, token string, invalidate func()) { + logger := zerolog.Ctx(w.ctx) + + expiry, err := jwtExpiry(token) + if err != nil { + logger.Warn().Err(err).Msgf("could not parse %s token JWT expiry; token will not auto-invalidate", name) + return + } + + warnIn := time.Until(expiry) - expiryWarning + if warnIn <= 0 { + logger.Warn().Msgf("%s token already within warning window; marking invalid", name) + invalidate() + return + } + + w.wg.Go(func() { + select { + case <-ctx.Done(): + // Token was rotated before the timer fired — nothing to do. + case <-time.After(warnIn): + invalidate() + logger.Warn().Msgf("%s token is about to expire", name) + } + }) +} + +func (w *Watcher) load() error { + data, err := os.ReadFile(w.tokensPath) + if err != nil { + return err + } + var t apiTokens + if err := json.Unmarshal(data, &t); err != nil { + return err + } + + w.mu.Lock() + + // Mark all tokens valid and cancel any in-flight expiry goroutines. + w.nvcf.value, w.nvcf.valid = t.NvcfApiToken, true + w.nvct.value, w.nvct.valid = t.NvctApiToken, true + w.sis.value, w.sis.valid = t.SisApiToken, true + + if w.nvcf.cancel != nil { + w.nvcf.cancel() + } + if w.nvct.cancel != nil { + w.nvct.cancel() + } + if w.sis.cancel != nil { + w.sis.cancel() + } + + // Fresh per-token contexts for the new expiry goroutines. + nvcfCtx, nvcfCancel := context.WithCancel(w.ctx) + nvctCtx, nvctCancel := context.WithCancel(w.ctx) + sisCtx, sisCancel := context.WithCancel(w.ctx) + w.nvcf.cancel = nvcfCancel + w.nvct.cancel = nvctCancel + w.sis.cancel = sisCancel + + w.mu.Unlock() + + w.startExpiryWatcher(nvcfCtx, "nvcf", t.NvcfApiToken, func() { w.mu.Lock(); w.nvcf.valid = false; w.mu.Unlock() }) + w.startExpiryWatcher(nvctCtx, "nvct", t.NvctApiToken, func() { w.mu.Lock(); w.nvct.valid = false; w.mu.Unlock() }) + w.startExpiryWatcher(sisCtx, "sis", t.SisApiToken, func() { w.mu.Lock(); w.sis.valid = false; w.mu.Unlock() }) + + return nil +} + +func newWatcher(ctx context.Context, path string) *Watcher { + logger := zerolog.Ctx(ctx) + // Canonicalize so it matches filepath.Clean(event.Name) from the dir watch. + path = filepath.Clean(path) + w := &Watcher{ctx: ctx, tokensPath: path} + + if err := w.load(); err != nil { + logger.Fatal().Err(err).Msg("failed to load API tokens") + } + + fsw, err := fsnotify.NewWatcher() + if err != nil { + logger.Fatal().Err(err).Msg("failed to create file watcher") + } + + // Watch the parent directory rather than the file itself. OpenBao Agent + // renders tokens atomically (write-temp-then-rename), so every rotation + // swaps in a new inode at `path`. A watch on the file's inode fires at most + // once and then goes silent; a watch on the (stable) directory inode sees + // the rename as a Create/Write on the target path across every rotation. + watchDir := filepath.Dir(path) + if err := fsw.Add(watchDir); err != nil { + logger.Fatal().Err(err).Msg("failed to add directory to watcher") + } + + w.wg.Go(func() { + defer func() { _ = fsw.Close() }() + for { + select { + case <-ctx.Done(): + return + case event := <-fsw.Events: + // Directory watch reports every entry; only react to our file. + if filepath.Clean(event.Name) != path { + continue + } + if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) { + if err := w.load(); err != nil { + logger.Error().Err(err).Msg("failed to reload API tokens") + } + } + case err := <-fsw.Errors: + logger.Error().Err(err).Msg("file watcher error") + } + } + }) + + return w +} + +// Watch loads tokens from TokensFilePath on startup and reloads them +// whenever the file is written or recreated (e.g. by the OpenBao agent). +// Expects a zerolog.Logger stored in ctx via zerolog.WithContext. +func Watch(ctx context.Context) *Watcher { + return newWatcher(ctx, utils.GetEnvOr(tokensFilePath, defaultTokensFilePath)) +} diff --git a/src/uis/nvcf-ui/backend/internal/token-watcher/watcher_test.go b/src/uis/nvcf-ui/backend/internal/token-watcher/watcher_test.go new file mode 100644 index 000000000..9f97cf301 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/token-watcher/watcher_test.go @@ -0,0 +1,444 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tokenwatcher + +import ( + "context" + "encoding/base64" + "encoding/json" + "os" + "path/filepath" + "testing" + "time" + + "github.com/rs/zerolog" +) + +// makeJWT crafts a minimal, structurally valid JWT with the given exp Unix timestamp. +// The signature segment is a placeholder — jwtExpiry only parses the payload. +func makeJWT(exp int64) string { + header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + payload, _ := json.Marshal(map[string]int64{"exp": exp}) + return header + "." + base64.RawURLEncoding.EncodeToString(payload) + ".fakesig" +} + +func writeTokens(t *testing.T, path string, tokens apiTokens) { + t.Helper() + data, err := json.Marshal(tokens) + if err != nil { + t.Fatalf("marshal tokens: %v", err) + } + if err := os.WriteFile(path, data, 0o600); err != nil { + t.Fatalf("write tokens file: %v", err) + } +} + +func nopCtx(t *testing.T) context.Context { + t.Helper() + ctx, cancel := context.WithCancel(zerolog.New(os.Stderr).WithContext(context.Background())) + t.Cleanup(cancel) + return ctx +} + +// ---- jwtExpiry --------------------------------------------------------- + +func TestJwtExpiry(t *testing.T) { + t.Parallel() + + exp := time.Now().Add(time.Hour).Unix() + + tests := []struct { + name string + token string + wantErr bool + wantExp int64 + }{ + { + name: "valid JWT returns correct expiry", + token: makeJWT(exp), + wantExp: exp, + }, + { + name: "only two segments", + token: "header.payload", + wantErr: true, + }, + { + name: "invalid base64 in payload", + token: "header.!!!.sig", + wantErr: true, + }, + { + name: "payload not JSON", + token: "h." + base64.RawURLEncoding.EncodeToString([]byte("notjson")) + ".s", + wantErr: true, + }, + { + name: "missing exp claim", + token: makeJWT(0), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := jwtExpiry(tt.token) + if (err != nil) != tt.wantErr { + t.Fatalf("jwtExpiry err = %v, wantErr %v", err, tt.wantErr) + } + if err == nil && got.Unix() != tt.wantExp { + t.Errorf("expiry = %d, want %d", got.Unix(), tt.wantExp) + } + }) + } +} + +// ---- load -------------------------------------------------------------- + +func TestLoad(t *testing.T) { + t.Parallel() + + longJWT := makeJWT(time.Now().Add(24 * time.Hour).Unix()) + + tokens := apiTokens{ + NvcfApiToken: longJWT, + NvctApiToken: longJWT, + SisApiToken: longJWT, + } + + tests := []struct { + name string + setup func(path string) + wantErr bool + }{ + { + name: "valid file sets tokens and valid flags", + setup: func(path string) { + writeTokens(t, path, tokens) + }, + }, + { + name: "missing file returns error", + setup: func(path string) { /* don't create the file */ }, + wantErr: true, + }, + { + name: "invalid JSON returns error", + setup: func(path string) { + _ = os.WriteFile(path, []byte("{notjson"), 0o600) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "tokens.json") + tt.setup(path) + + w := &Watcher{ctx: nopCtx(t), tokensPath: path} + err := w.load() + + if (err != nil) != tt.wantErr { + t.Fatalf("load err = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + return + } + + nvcf, nvcfOK := w.NVCFToken() + nvct, nvctOK := w.NVCTToken() + sis, sisOK := w.SISToken() + + if nvcf != longJWT || !nvcfOK { + t.Errorf("NVCFToken = (%q, %v), want (%q, true)", nvcf, nvcfOK, longJWT) + } + if nvct != longJWT || !nvctOK { + t.Errorf("NVCTToken = (%q, %v), want (%q, true)", nvct, nvctOK, longJWT) + } + if sis != longJWT || !sisOK { + t.Errorf("SISToken = (%q, %v), want (%q, true)", sis, sisOK, longJWT) + } + }) + } +} + +// ---- expiry timer ------------------------------------------------------ + +func TestExpiryTimerInvalidatesToken(t *testing.T) { + t.Parallel() + + // Token expires just past the warning window so the timer fires in ~1.5s. + // The margin must exceed 1s to survive Unix timestamp truncation. + exp := time.Now().Add(expiryWarning + 1500*time.Millisecond) + jwt := makeJWT(exp.Unix()) + + path := filepath.Join(t.TempDir(), "tokens.json") + writeTokens(t, path, apiTokens{ + NvcfApiToken: jwt, + NvctApiToken: jwt, + SisApiToken: jwt, + }) + + w := &Watcher{ctx: nopCtx(t), tokensPath: path} + if err := w.load(); err != nil { + t.Fatalf("load: %v", err) + } + + // All tokens valid immediately after load. + if _, ok := w.NVCFToken(); !ok { + t.Fatal("expected NVCFToken to be valid after load") + } + + // Wait for all three expiry timers to fire. + deadline := time.Now().Add(4 * time.Second) + for time.Now().Before(deadline) { + _, nvcfOK := w.NVCFToken() + _, nvctOK := w.NVCTToken() + _, sisOK := w.SISToken() + if !nvcfOK && !nvctOK && !sisOK { + break + } + time.Sleep(10 * time.Millisecond) + } + + if _, ok := w.NVCFToken(); ok { + t.Error("NVCFToken should be invalid after expiry warning window") + } + if _, ok := w.NVCTToken(); ok { + t.Error("NVCTToken should be invalid after expiry warning window") + } + if _, ok := w.SISToken(); ok { + t.Error("SISToken should be invalid after expiry warning window") + } +} + +// ---- token rotation cancels old expiry goroutine ----------------------- + +func TestRotationCancelsExpiryGoroutine(t *testing.T) { + t.Parallel() + + // First load: token expires just past warning window (~1.5s timer). + shortExp := time.Now().Add(expiryWarning + 1500*time.Millisecond) + shortJWT := makeJWT(shortExp.Unix()) + + // Second load: token valid for 24h — expiry goroutine won't fire in test. + longJWT := makeJWT(time.Now().Add(24 * time.Hour).Unix()) + + path := filepath.Join(t.TempDir(), "tokens.json") + writeTokens(t, path, apiTokens{ + NvcfApiToken: shortJWT, + NvctApiToken: shortJWT, + SisApiToken: shortJWT, + }) + + w := &Watcher{ctx: nopCtx(t), tokensPath: path} + if err := w.load(); err != nil { + t.Fatalf("first load: %v", err) + } + + // Rotate tokens before the short-expiry timer fires. + writeTokens(t, path, apiTokens{ + NvcfApiToken: longJWT, + NvctApiToken: longJWT, + SisApiToken: longJWT, + }) + if err := w.load(); err != nil { + t.Fatalf("second load: %v", err) + } + + // Wait longer than the original timer would have fired (1.5s + buffer). + time.Sleep(2500 * time.Millisecond) + + // Tokens must still be valid — the old goroutine was cancelled. + if _, ok := w.NVCFToken(); !ok { + t.Error("NVCFToken should still be valid after rotation") + } + if _, ok := w.NVCTToken(); !ok { + t.Error("NVCTToken should still be valid after rotation") + } + if _, ok := w.SISToken(); !ok { + t.Error("SISToken should still be valid after rotation") + } +} + +// ---- file watcher integration ------------------------------------------ + +func TestWatchReloadsOnFileChange(t *testing.T) { + t.Parallel() + + longJWT := makeJWT(time.Now().Add(24 * time.Hour).Unix()) + updatedJWT := makeJWT(time.Now().Add(48 * time.Hour).Unix()) + + path := filepath.Join(t.TempDir(), "tokens.json") + writeTokens(t, path, apiTokens{ + NvcfApiToken: longJWT, + NvctApiToken: longJWT, + SisApiToken: longJWT, + }) + + ctx, cancel := context.WithCancel(zerolog.New(os.Stderr).WithContext(context.Background())) + t.Cleanup(cancel) + + w := newWatcher(ctx, path) + + if token, _ := w.NVCFToken(); token != longJWT { + t.Fatalf("initial NVCFToken = %q, want %q", token, longJWT) + } + + // Overwrite the file to simulate an OpenBao token rotation. + writeTokens(t, path, apiTokens{ + NvcfApiToken: updatedJWT, + NvctApiToken: updatedJWT, + SisApiToken: updatedJWT, + }) + + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + if token, _ := w.NVCFToken(); token == updatedJWT { + break + } + time.Sleep(10 * time.Millisecond) + } + + if token, _ := w.NVCFToken(); token != updatedJWT { + t.Errorf("NVCFToken after rotation = %q, want %q", token, updatedJWT) + } +} + +// writeTokensAtomic renders the tokens file the way OpenBao Agent does: write +// to a temp file in the same directory, then rename(2) over the destination. +// Every call installs a new inode at `path`, which is what breaks a watch on +// the file's inode (vs. a watch on the parent directory). +func writeTokensAtomic(t *testing.T, path string, tokens apiTokens) { + t.Helper() + data, err := json.Marshal(tokens) + if err != nil { + t.Fatalf("marshal tokens: %v", err) + } + tmp := path + ".tmp" + if err := os.WriteFile(tmp, data, 0o600); err != nil { + t.Fatalf("write temp tokens file: %v", err) + } + if err := os.Rename(tmp, path); err != nil { + t.Fatalf("rename tokens file: %v", err) + } +} + +// TestWatchReloadsOnAtomicRename mirrors production: OpenBao rotates the token +// file via temp-then-rename, swapping the inode on every render. A file-inode +// watch would fire at most once and then go silent; the directory watch must +// pick up every rotation. Two rotations catch the "works once, then stops" mode. +func TestWatchReloadsOnAtomicRename(t *testing.T) { + t.Parallel() + + jwt1 := makeJWT(time.Now().Add(24 * time.Hour).Unix()) + jwt2 := makeJWT(time.Now().Add(48 * time.Hour).Unix()) + jwt3 := makeJWT(time.Now().Add(72 * time.Hour).Unix()) + + path := filepath.Join(t.TempDir(), "tokens.json") + writeTokensAtomic(t, path, apiTokens{NvcfApiToken: jwt1, NvctApiToken: jwt1, SisApiToken: jwt1}) + + ctx, cancel := context.WithCancel(zerolog.New(os.Stderr).WithContext(context.Background())) + t.Cleanup(cancel) + + w := newWatcher(ctx, path) + if token, _ := w.NVCFToken(); token != jwt1 { + t.Fatalf("initial NVCFToken = %q, want %q", token, jwt1) + } + + waitForToken := func(want string) { + t.Helper() + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + if token, _ := w.NVCFToken(); token == want { + return + } + time.Sleep(10 * time.Millisecond) + } + token, _ := w.NVCFToken() + t.Fatalf("NVCFToken after atomic rename = %q, want %q", token, want) + } + + writeTokensAtomic(t, path, apiTokens{NvcfApiToken: jwt2, NvctApiToken: jwt2, SisApiToken: jwt2}) + waitForToken(jwt2) + + // The second rotation is the one a file-inode watch would miss. + writeTokensAtomic(t, path, apiTokens{NvcfApiToken: jwt3, NvctApiToken: jwt3, SisApiToken: jwt3}) + waitForToken(jwt3) +} + +// ---- Wait blocks until goroutines exit --------------------------------- + +func TestWaitReturnsAfterContextCancel(t *testing.T) { + t.Parallel() + + longJWT := makeJWT(time.Now().Add(24 * time.Hour).Unix()) + + path := filepath.Join(t.TempDir(), "tokens.json") + writeTokens(t, path, apiTokens{ + NvcfApiToken: longJWT, + NvctApiToken: longJWT, + SisApiToken: longJWT, + }) + + ctx, cancel := context.WithCancel(zerolog.New(os.Stderr).WithContext(context.Background())) + w := newWatcher(ctx, path) + + // Cancelling the context must make the fsnotify loop and every per-token + // expiry timer observe ctx.Done() and exit, so Wait() returns promptly. + cancel() + + done := make(chan struct{}) + go func() { + w.Wait() + close(done) + }() + + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("Wait did not return after context cancellation — a goroutine is hanging") + } +} + +// ---- Watch resolves the tokens path from the environment --------------- + +func TestWatchResolvesPathFromEnv(t *testing.T) { + // Not parallel: t.Setenv mutates process-global state. + longJWT := makeJWT(time.Now().Add(24 * time.Hour).Unix()) + + path := filepath.Join(t.TempDir(), "tokens.json") + writeTokens(t, path, apiTokens{ + NvcfApiToken: longJWT, + NvctApiToken: longJWT, + SisApiToken: longJWT, + }) + + t.Setenv(tokensFilePath, path) + + ctx, cancel := context.WithCancel(zerolog.New(os.Stderr).WithContext(context.Background())) + t.Cleanup(cancel) + + w := Watch(ctx) + + if w.tokensPath != path { + t.Errorf("tokensPath = %q, want %q (from %s env var)", w.tokensPath, path, tokensFilePath) + } + if token, ok := w.NVCFToken(); token != longJWT || !ok { + t.Errorf("NVCFToken = (%q, %v), want (%q, true)", token, ok, longJWT) + } +} diff --git a/src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel new file mode 100644 index 000000000..612c427ae --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "utils", + srcs = ["utils.go"], + importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/utils", + visibility = ["//backend:__subpackages__"], + deps = [ + "@com_github_rs_zerolog//:zerolog", + "@io_k8s_apimachinery//pkg/runtime", + "@io_k8s_client_go//kubernetes/scheme", + "@io_k8s_sigs_controller_runtime//:controller-runtime", + "@io_k8s_sigs_controller_runtime//pkg/client", + ], +) + +go_test( + name = "utils_test", + srcs = ["utils_test.go"], + embed = [":utils"], + deps = ["@com_github_rs_zerolog//:zerolog"], +) + + +filegroup( + name = "sources", + srcs = glob(["*.go", "*.md"], allow_empty = True), + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/backend/internal/utils/utils.go b/src/uis/nvcf-ui/backend/internal/utils/utils.go new file mode 100644 index 000000000..017ba8f6a --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/utils/utils.go @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "fmt" + "os" + + "github.com/rs/zerolog" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const logLevel = "LOG_LEVEL" + +// GetEnvOr returns the value of the environment variable named by key, or +// fallback if the variable is unset or empty. +func GetEnvOr(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} + +func ConfigLogger() zerolog.Logger { + logger := zerolog.New(os.Stdout).With().Caller().Timestamp().Logger() + // To disable logging entirely, pass [zerolog.Disabled] + logLevel, err := zerolog.ParseLevel(os.Getenv(logLevel)) + if err != nil { + logger.Fatal().Err(err).Msgf("Invalid %s", logLevel) + } + if logLevel.String() == "" { + logLevel = zerolog.InfoLevel + } + + return logger.Level(logLevel) +} + +func InitK8sClient() (client.Client, error) { + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("register k8s scheme: %w", err) + } + cfg, err := ctrl.GetConfig() + if err != nil { + return nil, fmt.Errorf("get k8s config: %w", err) + } + k8sClient, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + return nil, fmt.Errorf("create k8s client: %w", err) + } + return k8sClient, nil +} diff --git a/src/uis/nvcf-ui/backend/internal/utils/utils_test.go b/src/uis/nvcf-ui/backend/internal/utils/utils_test.go new file mode 100644 index 000000000..004fa6a75 --- /dev/null +++ b/src/uis/nvcf-ui/backend/internal/utils/utils_test.go @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "os" + "testing" + + "github.com/rs/zerolog" +) + +func TestGetEnvOr(t *testing.T) { + const key = "NVCF_UI_SHIM_TEST_ENV" + + tests := []struct { + name string + set bool + value string + fallback string + want string + }{ + {name: "unset returns fallback", set: false, fallback: "def", want: "def"}, + {name: "empty returns fallback", set: true, value: "", fallback: "def", want: "def"}, + {name: "set returns value", set: true, value: "custom", fallback: "def", want: "custom"}, + {name: "set wins over empty fallback", set: true, value: "v", fallback: "", want: "v"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.set { + t.Setenv(key, tt.value) + } else { + // t.Setenv requires a value; register cleanup via it, then unset + // so the variable is genuinely absent for this case. + t.Setenv(key, "sentinel") + if err := os.Unsetenv(key); err != nil { + t.Fatalf("unset: %v", err) + } + } + + if got := GetEnvOr(key, tt.fallback); got != tt.want { + t.Errorf("GetEnvOr(%q, %q) = %q, want %q", key, tt.fallback, got, tt.want) + } + }) + } +} + +func TestConfigLogger(t *testing.T) { + tests := []struct { + name string + level string + want zerolog.Level + wantFatal bool // invalid level => logger.Fatal() + }{ + {name: "empty defaults to info", level: "", want: zerolog.InfoLevel}, + {name: "debug", level: "debug", want: zerolog.DebugLevel}, + {name: "warn", level: "warn", want: zerolog.WarnLevel}, + {name: "error", level: "error", want: zerolog.ErrorLevel}, + {name: "invalid level is fatal", level: "bogus", wantFatal: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(logLevel, tt.level) + + if tt.wantFatal { + // zerolog's Fatal() calls os.Exit(1) by default, which recover() + // cannot catch. Redirect it to panic so the branch runs in-process + // (and is covered), then recover the panic to assert it fired. + origExit := zerolog.FatalExitFunc + zerolog.FatalExitFunc = func() { panic("fatal") } + t.Cleanup(func() { zerolog.FatalExitFunc = origExit }) + + // Fatal() also closes its writer; ConfigLogger logs to os.Stdout, + // so redirect it to a throwaway file to spare the test runner's + // real stdout, and restore it afterwards. + realStdout := os.Stdout + f, err := os.CreateTemp(t.TempDir(), "stdout") + if err != nil { + t.Fatalf("create temp stdout: %v", err) + } + os.Stdout = f + t.Cleanup(func() { os.Stdout = realStdout }) + + defer func() { + if recover() == nil { + t.Errorf("ConfigLogger(%q) did not fatal", tt.level) + } + }() + ConfigLogger() + return + } + + if got := ConfigLogger().GetLevel(); got != tt.want { + t.Errorf("ConfigLogger level = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/uis/nvcf-ui/biome.json b/src/uis/nvcf-ui/biome.json new file mode 100644 index 000000000..683d1c738 --- /dev/null +++ b/src/uis/nvcf-ui/biome.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.5/schema.json", + "extends": ["./ui/biome.json"] +} diff --git a/src/uis/nvcf-ui/go.work.bazel b/src/uis/nvcf-ui/go.work.bazel new file mode 100644 index 000000000..4864ce4cf --- /dev/null +++ b/src/uis/nvcf-ui/go.work.bazel @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Bazel-scope Go workspace consumed by gazelle's go_deps (via +// go_deps.from_file). Named .bazel so it does not affect the backend's tooling. +go 1.26.2 + +use ( + ./backend +) diff --git a/src/uis/nvcf-ui/helm/.helmignore b/src/uis/nvcf-ui/helm/.helmignore new file mode 100644 index 000000000..73167f78b --- /dev/null +++ b/src/uis/nvcf-ui/helm/.helmignore @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/src/uis/nvcf-ui/helm/Chart.yaml b/src/uis/nvcf-ui/helm/Chart.yaml new file mode 100644 index 000000000..4264134ea --- /dev/null +++ b/src/uis/nvcf-ui/helm/Chart.yaml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v2 +name: helm-nvcf-ui +description: NVCF UI — Graphical User Interface for NVIDIA Cloud Foundation +type: application + +# Chart version. Autoversioned by the release pipeline. +version: 0.1.0 + +# The nvcf-ui image tag defaults to this appVersion; the release pipeline stamps +# it to the release version at package time. +appVersion: "0.1.0" diff --git a/src/uis/nvcf-ui/helm/templates/NOTES.txt b/src/uis/nvcf-ui/helm/templates/NOTES.txt new file mode 100644 index 000000000..c521a9119 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/NOTES.txt @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +1. Get the application URL by running these commands: +{{- if .Values.nvcfUi.httpRoute.enabled }} +{{- if .Values.nvcfUi.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.nvcfUi.httpRoute.hostnames | first }} +{{- else }} + export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.nvcfUi.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.nvcfUi.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") + {{- end }} +{{- if and .Values.nvcfUi.httpRoute.rules (first .Values.nvcfUi.httpRoute.rules).matches (first (first .Values.nvcfUi.httpRoute.rules).matches).path.value }} + echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.nvcfUi.httpRoute.rules).matches).path.value }} to use your application" + + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.nvcfUi.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.nvcfUi.httpRoute.parentRefs).name }} -o yaml' +{{- end }} +{{- else if contains "NodePort" .Values.nvcfUi.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nvcf-ui.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.nvcfUi.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nvcf-ui.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nvcf-ui.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.nvcfUi.service.port }} +{{- else if contains "ClusterIP" .Values.nvcfUi.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nvcf-ui.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/src/uis/nvcf-ui/helm/templates/_helpers.tpl b/src/uis/nvcf-ui/helm/templates/_helpers.tpl new file mode 100644 index 000000000..fd93b96a3 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/_helpers.tpl @@ -0,0 +1,171 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{/* +Expand the name of the chart. +*/}} +{{- define "nvcf-ui.name" -}} +{{- default .Chart.Name .Values.nvcfUi.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "nvcf-ui.fullname" -}} +{{- if .Values.nvcfUi.fullnameOverride }} +{{- .Values.nvcfUi.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nvcfUi.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nvcf-ui.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nvcf-ui.labels" -}} +helm.sh/chart: {{ include "nvcf-ui.chart" . }} +{{ include "nvcf-ui.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nvcf-ui.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nvcf-ui.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Name of the service account to use. Hardcoded to "nvcf-ui": it must match the +serviceaccount/name bound_claim in the OpenBao nvcf-ui JWT auth role (see the +openbao migrations addon setup_nvcf-ui.sh), or Vault Agent auth fails. Not +overridable on purpose — the two sides share this fixed contract. +*/}} +{{- define "nvcf-ui.serviceAccountName" -}} +nvcf-ui +{{- end }} + +{{/* +nvcf-openbao-migrations image reference for the opt-in OpenBao roles hook Job. +Registry is optional; repository and tag are required when the hook is enabled. +*/}} +{{- define "nvcf-ui.openbaoMigrationsImage" -}} +{{- $img := .Values.nvcfUi.openbaoMigrations.image -}} +{{- $registry := $img.registry -}} +{{- $repository := required "openbaoMigrations.image.repository is required" $img.repository -}} +{{- $tag := required "openbaoMigrations.image.tag is required" $img.tag -}} +{{- if $registry -}} +{{- printf "%s/%s:%s" $registry $repository $tag -}} +{{- else -}} +{{- printf "%s:%s" $repository $tag -}} +{{- end -}} +{{- end }} + +{{/* +Control-plane fully qualified name. +*/}} +{{- define "nvcf-ui.controlPlane.fullname" -}} +{{- printf "%s-control-plane" (include "nvcf-ui.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Control-plane selector labels. Adds a component label so this Deployment's +selector never matches the server pods (and vice versa). +*/}} +{{- define "nvcf-ui.controlPlane.selectorLabels" -}} +{{ include "nvcf-ui.selectorLabels" . }} +app.kubernetes.io/component: control-plane +{{- end }} + +{{/* +Control-plane common labels. +*/}} +{{- define "nvcf-ui.controlPlane.labels" -}} +{{ include "nvcf-ui.labels" . }} +app.kubernetes.io/component: control-plane +{{- end }} + +{{/* +Create the name of the control-plane service account to use. It is always +created; an explicit name overrides the generated default. +*/}} +{{- define "nvcf-ui.controlPlane.serviceAccountName" -}} +{{- default (include "nvcf-ui.controlPlane.fullname" .) .Values.nvcfUi.controlPlane.serviceAccount.name }} +{{- end }} + +{{/* +This is the OpenBao-audience +Kubernetes token used for Vault Agent auto-auth; it is distinct from the normal +kube API service-account token and from any Vault Agent output token. +*/}} +{{- define "nvcf-ui.openbaoTokenVolumeName" -}} +openbao-token +{{- end -}} + +{{- define "nvcf-ui.openbaoTokenMountPath" -}} +/var/run/secrets/openbao +{{- end -}} + +{{- define "nvcf-ui.openbaoTokenFileName" -}} +token +{{- end -}} + +{{- define "nvcf-ui.openbaoTokenPath" -}} +{{- printf "%s/%s" (include "nvcf-ui.openbaoTokenMountPath" .) (include "nvcf-ui.openbaoTokenFileName" .) -}} +{{- end -}} + +{{/* +OpenBao agent-injector annotations. +The agent mints JWTs against nvcf-api, nvct-api, and sis-api via templates +and writes them to /var/run/secrets/vault/tokens.json. The app reads that file; +the agent handles re-minting on expiry. +*/}} +{{- define "nvcf-ui.vaultAnnotations" -}} +vault.hashicorp.com/agent-inject: "true" +vault.hashicorp.com/role: {{ .Values.nvcfUi.vault.role | quote }} +vault.hashicorp.com/auth-path: {{ .Values.nvcfUi.vault.authPath | quote }} +{{- /* Authenticate with the chart-owned, audience-scoped projected token rather +than the default kube SA token, whose aud the jwt role rejects. +The app container mounts this projected token at openbaoTokenMountPath; +agent-copy-volume-mounts (below) copies that mount into the agent init/sidecar, +so the token is available to the agent at the same path. auth-config-token-path is +the file the jwt auth method reads. Do NOT also set +agent-service-account-token-volume-name: it makes the injector add a second mount +of the same volume at the same path, which makes the pod invalid (mountPath must +be unique). */}} +vault.hashicorp.com/auth-config-token-path: {{ include "nvcf-ui.openbaoTokenPath" . | quote }} +vault.hashicorp.com/agent-run-as-same-user: "true" +vault.hashicorp.com/agent-inject-template-file-tokens.json: "/vault/config/templates/tokens.tmpl" +vault.hashicorp.com/secret-volume-path: {{ .Values.nvcfUi.vault.secretVolumePath | quote }} +vault.hashicorp.com/agent-copy-volume-mounts: {{ .Chart.Name }} +{{- end }} diff --git a/src/uis/nvcf-ui/helm/templates/control-plane-components-configmap.yaml b/src/uis/nvcf-ui/helm/templates/control-plane-components-configmap.yaml new file mode 100644 index 000000000..bd246ffe9 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/control-plane-components-configmap.yaml @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "nvcf-ui.controlPlane.fullname" . }}-components + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 4 }} +data: + # Rendered from .Values.nvcfUi.controlPlane.components and mounted into the + # control-plane pod at COMPONENTS_PATH; Loadcomponents reads it at startup. + components.yaml: | + components: + {{- toYaml .Values.nvcfUi.controlPlane.components | nindent 6 }} diff --git a/src/uis/nvcf-ui/helm/templates/control-plane-deployment.yaml b/src/uis/nvcf-ui/helm/templates/control-plane-deployment.yaml new file mode 100644 index 000000000..8cf17930d --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/control-plane-deployment.yaml @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "nvcf-ui.controlPlane.fullname" . }} + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "nvcf-ui.controlPlane.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + # Roll the pods when the components config changes so the monitor + # re-reads it (Loadcomponents runs once at startup). + checksum/components: {{ include (print $.Template.BasePath "/control-plane-components-configmap.yaml") . | sha256sum | quote }} + {{- with .Values.nvcfUi.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 8 }} + {{- with .Values.nvcfUi.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.nvcfUi.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "nvcf-ui.controlPlane.serviceAccountName" . }} + {{- with .Values.nvcfUi.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: control-plane + {{- with .Values.nvcfUi.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.nvcfUi.image.repository }}:{{ .Values.nvcfUi.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.nvcfUi.image.pullPolicy }} + # Run the control-plane monitor binary (overrides the image ENTRYPOINT, + # which is the server binary). + command: + - /app/control-plane + env: + # The monitor writes its health ConfigMap into the namespace it runs + # in; NAMESPACE is read by configMapNamespace(). + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + # File the monitor reads its components config from; provided by the + # components ConfigMap mounted at /etc/backend below. + - name: COMPONENTS_PATH + value: /etc/backend/components.yaml + {{- with .Values.nvcfUi.controlPlane.env }} + {{- toYaml . | nindent 12 }} + {{- end }} + # exec probes (no HTTP server): the kubelet re-runs the binary in + # -liveness mode, which checks the monitor's heartbeat file is fresh. + # The exec command is hardcoded; timing comes from values. + livenessProbe: + exec: + command: + - /app/control-plane + - -liveness + {{- toYaml .Values.nvcfUi.controlPlane.livenessProbe | nindent 12 }} + readinessProbe: + exec: + command: + - /app/control-plane + - -liveness + {{- toYaml .Values.nvcfUi.controlPlane.readinessProbe | nindent 12 }} + {{- with .Values.nvcfUi.controlPlane.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: components + mountPath: /etc/backend + readOnly: true + volumes: + - name: components + configMap: + name: {{ include "nvcf-ui.controlPlane.fullname" . }}-components + {{- with .Values.nvcfUi.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/src/uis/nvcf-ui/helm/templates/control-plane-rbac.yaml b/src/uis/nvcf-ui/helm/templates/control-plane-rbac.yaml new file mode 100644 index 000000000..ef3fc4113 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/control-plane-rbac.yaml @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Namespaced: the monitor writes its health ConfigMap only into its own +# namespace (NAMESPACE = metadata.namespace), so ConfigMap access stays scoped +# to the release namespace. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "nvcf-ui.controlPlane.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 4 }} +rules: + # Write the control-plane-health ConfigMap (server-side apply) and read it back. + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch", "create", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "nvcf-ui.controlPlane.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "nvcf-ui.controlPlane.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "nvcf-ui.controlPlane.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +# Cluster-scoped: components.yaml addresses workloads by their own namespace, so +# the monitor must read workloads and pods in ANY namespace to evaluate health +# (see k8shealth.go). These grants are read-only. The resource name is suffixed +# with the namespace so multiple releases in different namespaces don't collide +# on these cluster-scoped objects. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "nvcf-ui.controlPlane.fullname" . }}-{{ .Release.Namespace }} + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 4 }} +rules: + - apiGroups: ["apps"] + resources: ["deployments", "statefulsets", "daemonsets", "replicasets"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "nvcf-ui.controlPlane.fullname" . }}-{{ .Release.Namespace }} + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "nvcf-ui.controlPlane.fullname" . }}-{{ .Release.Namespace }} +subjects: + - kind: ServiceAccount + name: {{ include "nvcf-ui.controlPlane.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} diff --git a/src/uis/nvcf-ui/helm/templates/control-plane-serviceaccount.yaml b/src/uis/nvcf-ui/helm/templates/control-plane-serviceaccount.yaml new file mode 100644 index 000000000..dd5fa286e --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/control-plane-serviceaccount.yaml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nvcf-ui.controlPlane.serviceAccountName" . }} + labels: + {{- include "nvcf-ui.controlPlane.labels" . | nindent 4 }} + {{- with .Values.nvcfUi.controlPlane.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +# Required: the monitor authenticates to the API server via in-cluster config, +# which reads the mounted ServiceAccount token. +automountServiceAccountToken: true diff --git a/src/uis/nvcf-ui/helm/templates/deployment.yaml b/src/uis/nvcf-ui/helm/templates/deployment.yaml new file mode 100644 index 000000000..138da0077 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/deployment.yaml @@ -0,0 +1,137 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "nvcf-ui.fullname" . }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} +spec: + {{- if not .Values.nvcfUi.autoscaling.enabled }} + replicas: {{ .Values.nvcfUi.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "nvcf-ui.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + # Roll the pods when the vault token template changes: the agent renders + # tokens.json from it once at startup, so a ConfigMap-only change would + # otherwise leave running pods on the stale template. + checksum/vault-tpl: {{ include (print $.Template.BasePath "/vault-agent-configmap.yaml") . | sha256sum | quote }} + {{- include "nvcf-ui.vaultAnnotations" . | nindent 8 }} + {{- with .Values.nvcfUi.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nvcf-ui.labels" . | nindent 8 }} + {{- with .Values.nvcfUi.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.nvcfUi.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "nvcf-ui.serviceAccountName" . }} + {{- with .Values.nvcfUi.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.nvcfUi.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.nvcfUi.image.repository }}:{{ .Values.nvcfUi.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.nvcfUi.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.nvcfUi.service.port }} + protocol: TCP + env: + # The server reads the control-plane-health ConfigMap from its own + # namespace; NAMESPACE is read by configMapNamespace(). + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + # Upstream service addresses the BFF server proxies to. + {{- with .Values.nvcfUi.config.nvcfUrl }} + - name: NVCF_URL + value: {{ . | quote }} + {{- end }} + {{- with .Values.nvcfUi.config.nvctUrl }} + - name: NVCT_URL + value: {{ . | quote }} + {{- end }} + {{- with .Values.nvcfUi.config.sisUrl }} + - name: SIS_URL + value: {{ . | quote }} + {{- end }} + # HTTP probes against /status, the server's dependency-free 200 + # handler. The probe action is hardcoded; timing comes from values. + livenessProbe: + httpGet: + path: /status + port: http + {{- toYaml .Values.nvcfUi.livenessProbe | nindent 12 }} + readinessProbe: + httpGet: + path: /status + port: http + {{- toYaml .Values.nvcfUi.readinessProbe | nindent 12 }} + {{- with .Values.nvcfUi.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: vault-config-templates + mountPath: /vault/config/templates + - name: {{ include "nvcf-ui.openbaoTokenVolumeName" . }} + mountPath: {{ include "nvcf-ui.openbaoTokenMountPath" . }} + readOnly: true + {{- with .Values.nvcfUi.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: vault-config-templates + configMap: + name: {{ include "nvcf-ui.fullname" . }}-vault-agent-tpl + - name: {{ include "nvcf-ui.openbaoTokenVolumeName" . }} + projected: + sources: + - serviceAccountToken: + path: {{ include "nvcf-ui.openbaoTokenFileName" . }} + audience: {{ .Values.nvcfUi.vault.audience | quote }} + expirationSeconds: 3600 + {{- with .Values.nvcfUi.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/src/uis/nvcf-ui/helm/templates/hook-openbao-migrations.yaml b/src/uis/nvcf-ui/helm/templates/hook-openbao-migrations.yaml new file mode 100644 index 000000000..94c6fda19 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/hook-openbao-migrations.yaml @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Helm hook Job that provisions the OpenBao JWT signing roles the nvcf-ui BFF +# server needs to mint cluster, NVCF API, and NVCT tokens. Runs pre-install and +# pre-upgrade so the auth role and sign policies exist before the server pods +# come up and the Vault Agent tries to log in. +{{- $bao := .Values.nvcfUi.openbaoMigrations }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "nvcf-ui.fullname" . }}-openbao-migrations + namespace: {{ $bao.namespace }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} + app.kubernetes.io/component: openbao-migrations + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "2" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: {{ default 3 $bao.backoffLimit }} + template: + metadata: + labels: + {{- include "nvcf-ui.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: openbao-migrations + {{- with .Values.nvcfUi.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: OnFailure + serviceAccountName: {{ $bao.serviceAccountName }} + {{- with .Values.nvcfUi.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nvcfUi.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: openbao-migrations + {{- with $bao.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ include "nvcf-ui.openbaoMigrationsImage" . }}" + imagePullPolicy: {{ $bao.image.pullPolicy }} + env: + - name: CORE_MIGRATIONS_ENABLED + value: "false" + - name: ADDONS_NVCF_UI_ENABLED + value: "true" + - name: BAO_SERVICE + value: {{ ($bao.baoService | default (printf "openbao-server.%s.svc.cluster.local" $bao.namespace)) | quote }} + - name: BAO_PORT + value: {{ $bao.baoPort | quote }} + volumeMounts: + - name: root-token + mountPath: /secrets/root_token + readOnly: true + resources: + {{- toYaml $bao.resources | nindent 12 }} + volumes: + - name: root-token + secret: + secretName: {{ $bao.rootTokenSecretName }} + items: + - key: root_token + path: root_token diff --git a/src/uis/nvcf-ui/helm/templates/hpa.yaml b/src/uis/nvcf-ui/helm/templates/hpa.yaml new file mode 100644 index 000000000..efd51323b --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/hpa.yaml @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.nvcfUi.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "nvcf-ui.fullname" . }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "nvcf-ui.fullname" . }} + minReplicas: {{ .Values.nvcfUi.autoscaling.minReplicas }} + maxReplicas: {{ .Values.nvcfUi.autoscaling.maxReplicas }} + metrics: + {{- if .Values.nvcfUi.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.nvcfUi.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.nvcfUi.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.nvcfUi.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/src/uis/nvcf-ui/helm/templates/httproute.yaml b/src/uis/nvcf-ui/helm/templates/httproute.yaml new file mode 100644 index 000000000..701c1b728 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/httproute.yaml @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if .Values.nvcfUi.httpRoute.enabled }} +{{- $fullName := include "nvcf-ui.fullname" . }} +{{- $svcPort := .Values.nvcfUi.service.port }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + {{- with .Values.nvcfUi.httpRoute.namespace }} + namespace: {{ . }} + {{- end }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} + {{- with .Values.nvcfUi.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.nvcfUi.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.nvcfUi.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.nvcfUi.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + namespace: {{ $.Release.Namespace }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} diff --git a/src/uis/nvcf-ui/helm/templates/rbac.yaml b/src/uis/nvcf-ui/helm/templates/rbac.yaml new file mode 100644 index 000000000..d05c7a79c --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/rbac.yaml @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Namespaced read-only access for the BFF server: it serves GET /v1/control-plane +# by reading the control-plane-health ConfigMap from its own namespace (the one +# the control-plane monitor writes into). No cross-namespace access is needed. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "nvcf-ui.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "nvcf-ui.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "nvcf-ui.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "nvcf-ui.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} diff --git a/src/uis/nvcf-ui/helm/templates/referencegrant.yaml b/src/uis/nvcf-ui/helm/templates/referencegrant.yaml new file mode 100644 index 000000000..597c64918 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/referencegrant.yaml @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{{- if and .Values.nvcfUi.httpRoute.enabled .Values.nvcfUi.httpRoute.referenceGrant.enabled .Values.nvcfUi.httpRoute.namespace (ne .Values.nvcfUi.httpRoute.namespace .Release.Namespace) }} +# Gateway API requires a ReferenceGrant in the target (Service) namespace to +# authorize any cross-namespace backendRef. The HTTPRoute lives in the Gateway's +# namespace (.Values.nvcfUi.httpRoute.namespace), so this grant — created in the release +# namespace alongside the Service it authorizes — lets that route reach the +# in-release Service. Mirrors the cluster's allow-routes-to-* grants. +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: {{ include "nvcf-ui.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} +spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: {{ .Values.nvcfUi.httpRoute.namespace }} + to: + - group: "" + kind: Service + name: {{ include "nvcf-ui.fullname" . }} +{{- end }} diff --git a/src/uis/nvcf-ui/helm/templates/service.yaml b/src/uis/nvcf-ui/helm/templates/service.yaml new file mode 100644 index 000000000..b3f52a571 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/service.yaml @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nvcf-ui.fullname" . }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} +spec: + type: {{ .Values.nvcfUi.service.type }} + ports: + - port: {{ .Values.nvcfUi.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "nvcf-ui.selectorLabels" . | nindent 4 }} diff --git a/src/uis/nvcf-ui/helm/templates/serviceaccount.yaml b/src/uis/nvcf-ui/helm/templates/serviceaccount.yaml new file mode 100644 index 000000000..136c2e7eb --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/serviceaccount.yaml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nvcf-ui.serviceAccountName" . }} + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} + {{- with .Values.nvcfUi.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +# Required: the server reads the control-plane-health ConfigMap via in-cluster +# config, which reads the mounted ServiceAccount token. +automountServiceAccountToken: true diff --git a/src/uis/nvcf-ui/helm/templates/vault-agent-configmap.yaml b/src/uis/nvcf-ui/helm/templates/vault-agent-configmap.yaml new file mode 100644 index 000000000..cc70c97d9 --- /dev/null +++ b/src/uis/nvcf-ui/helm/templates/vault-agent-configmap.yaml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "nvcf-ui.fullname" . }}-vault-agent-tpl + labels: + {{- include "nvcf-ui.labels" . | nindent 4 }} +data: + tokens.tmpl: |- + {{- `{ + {{- with secret "services/nvcf-api/jwt/sign/nvcf-ui" }} + "nvcfApiToken": "{{ .Data.token }}", + {{- end }} + {{- with secret "services/nvct-api/jwt/sign/nvcf-ui" }} + "nvctApiToken": "{{ .Data.token }}", + {{- end }} + {{- with secret "services/sis-api/jwt/sign/nvcf-ui" }} + "sisApiToken": "{{ .Data.token }}" + {{- end }} +}` | nindent 4 }} diff --git a/src/uis/nvcf-ui/helm/values.yaml b/src/uis/nvcf-ui/helm/values.yaml new file mode 100644 index 000000000..72278fbfe --- /dev/null +++ b/src/uis/nvcf-ui/helm/values.yaml @@ -0,0 +1,386 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# All values are namespaced under the nvcfUi key so this flat chart can be +# fed a single nvcfUi block (matching helm-nvcf-grpc-proxy / helm-reval). +nvcfUi: + + # Default values for nvcf-ui. + # This is a YAML-formatted file. + # Declare variables to be passed into your templates. + + # This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ + replicaCount: 1 + + # This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ + image: + repository: nvcf-ui + # This sets the pull policy for images. + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + + # This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + imagePullSecrets: [] + # This is to override the chart name. + nameOverride: "" + fullnameOverride: "" + + # This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ + # A dedicated ServiceAccount and RBAC are always created. Token automounting is + # hardcoded on (the server needs it to read the control-plane-health ConfigMap). + serviceAccount: + # Annotations to add to the service account. + annotations: {} + # The name is hardcoded to "nvcf-ui" (see the serviceAccountName helper): + # it must match the OpenBao JWT auth role's bound_claims, so it is not + # overridable. + + # This is for setting Kubernetes Annotations to a Pod. + # For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + vault: + role: "nvcf-ui" + authPath: "auth/jwt" + secretVolumePath: "/var/run/secrets/vault" + # Audience the OpenBao jwt auth role expects (its bound_audiences). The agent + # presents a projected ServiceAccount token carrying this audience; it must + # match the server-side role config or login fails with "invalid audience + # (aud) claim". This is OpenBao's in-cluster address — the shared value the + # other NVCF services' roles are bound to. + audience: "http://openbao-server.vault-system.svc.cluster.local:8200" + + # Helm hook Job that provisions the nvcf-ui OpenBao JWT signing roles + # (cluster, NVCF API, NVCT) plus the auth role the vault block above logs in with. + # These are an optional addon in the nvcf-openbao-migrations image; core + # installs that do not deploy the UI never create them. The Job runs + # pre-install/pre-upgrade with CORE_MIGRATIONS_ENABLED=false + + # ADDONS_NVCF_UI_ENABLED=true so only addons/nvcf-ui/setup_nvcf-ui.sh runs. + # Mirrors the llm-request-router pki hook pattern. + openbaoMigrations: + # Namespace for the hook Job. Should match the openbao-server install + # namespace so the root-token secret and serviceAccount resolve. + namespace: vault-system + # backoffLimit for the hook Job. Transient failures retry via + # restartPolicy: OnFailure; deterministic failures exhaust this and the Job + # fails, blocking the release. + backoffLimit: 3 + # nvcf-openbao-migrations image. registry is optional; repository and tag are + # required when enabled. Supply via the stack's image overrides. + image: + registry: "" + repository: nvcf-openbao-migrations + tag: "0.16.2" + pullPolicy: IfNotPresent + # OpenBao connection. When baoService is empty, the template derives + # openbao-server..svc.cluster.local. + baoService: "" + baoPort: "8200" + # Service account with read access to the OpenBao root-token secret (created + # by the k8s-openbao chart). + serviceAccountName: openbao-server-initialize-cluster + # Secret holding the OpenBao root token, mounted at + # /secrets/root_token/root_token by the entrypoint. + rootTokenSecretName: openbao-server-root-token + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 256Mi + # Container securityContext for the hook Job. Its own knob (not the shared + # top-level nvcfUi.securityContext) because the hook runs the + # nvcf-openbao-migrations image, which may not support the server's + # runAsUser 65532. Empty by default so the image's own user/settings apply; + # set it to harden the hook. + securityContext: {} + + podAnnotations: {} + # This is for setting Kubernetes Labels to a Pod. + # For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + + podSecurityContext: {} + # fsGroup: 2000 + + # runAsUser MUST be set: the server deployment requests the OpenBao/Vault agent + # sidecar with agent-run-as-same-user=true (see _helpers.tpl), and the injector + # webhook rejects the pod ("No SecurityContext found for Container 0") unless the + # app container declares runAsUser. 65532 is the distroless "nonroot" UID/GID the + # image already runs as (see Dockerfile). + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + capabilities: + drop: + - ALL + + # This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ + service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + port: 8300 + + # Upstream service addresses the BFF server proxies to. Each is injected into the + # server container as an environment variable (NVCF_URL, NVCT_URL, SIS_URL). + config: + nvcfUrl: "api.nvcf.svc.cluster.local:8080" + nvctUrl: "nvct-api.nvcf.svc.cluster.local:8080" + sisUrl: "api.sis.svc.cluster.local:8080" + + # -- Expose the service via gateway-api HTTPRoute + # Requires Gateway API resources and a suitable controller in the cluster + # (see: https://gateway-api.sigs.k8s.io/guides/). + # Defaults mirror the cluster's live routes (the nvcf-gateway-routes chart): the + # route is created in the Gateway's namespace and attaches to the shared gateway, + # with a cross-namespace backendRef to the in-release Service authorized by the + # ReferenceGrant below. + httpRoute: + # HTTPRoute enabled. + enabled: false + # Namespace to create the HTTPRoute in. Set this to the Gateway's namespace to + # attach to a Gateway that lives elsewhere (the convention here); the backendRef + # to the in-release Service is then cross-namespace and requires the + # ReferenceGrant below. Leave empty to create the route in the release namespace + # (no ReferenceGrant needed). + namespace: envoy-gateway + # HTTPRoute annotations. + annotations: {} + # Which Gateways this Route is attached to. + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: nvcf-gateway + namespace: envoy-gateway + sectionName: http + # Hostnames matched by the route. + hostnames: + - nvcf-ui.localhost + # List of rules and filters applied. The template appends a backendRef to the + # in-release Service (cross-namespace, via the ReferenceGrant) to each rule. + rules: + - matches: + - path: + type: PathPrefix + value: / + # filters: + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 + # ReferenceGrant authorizing the cross-namespace HTTPRoute (in .namespace above) + # to reference the in-release Service. Created in the release namespace, only + # when .namespace differs from it. + referenceGrant: + enabled: false + + resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + # Liveness/readiness probe timing for the BFF server. The probe action is + # hardcoded in the Deployment template to an HTTP GET of /status (the server's + # dependency-free 200 handler); only these timing knobs are configurable. + # https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + livenessProbe: + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + + # control-plane runs the cluster health-monitor binary (cmd/control-plane) as a + # separate, headless Deployment that is always created. It has no Service or + # ports: it loops over the configured components, probes their health, and + # writes the result to the control-plane-health ConfigMap in its own namespace. + # It does not use Vault. Its replica count (1), entrypoint command, and exec + # probes are hardcoded in the Deployment template, not configurable here. + controlPlane: + serviceAccount: + # A dedicated ServiceAccount and RBAC are always created. Optionally override + # the name and add annotations. + name: "" + annotations: {} + resources: {} + # Probe timing for the control-plane liveness/readiness checks. The exec + # command (the binary in -liveness mode) is hardcoded in the Deployment + # template; only these timing knobs are configurable. The liveness + # periodSeconds * failureThreshold (60s) should track the heartbeat staleness + # window (3 x 20s). + livenessProbe: + initialDelaySeconds: 25 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 20 + timeoutSeconds: 5 + # Extra environment variables. NAMESPACE (= metadata.namespace) is always + # injected by the template. + env: [] + # Components the monitor probes each cycle. This list is rendered verbatim into + # the control-plane components ConfigMap and mounted at COMPONENTS_PATH; the + # binary reads it once at startup (changing it rolls the pods via a checksum + # annotation). See backend/internal/control-plane/endpoints.md for the + # full component inventory. + # + # Each component has a name and namespace, plus at least one of the following + # (both may be set, in which case the component is healthy only when both + # checks pass): + # endpoints: probed with an HTTP GET. Each endpoint must include the + # health path and port and omit the scheme (http:// is added); + # a 2xx response is healthy. + # k8s_workloads: probed by checking each workload's pods are ready, looked up + # in the component's namespace. type is one of deployment, + # statefulset, daemonset, replicaset. + components: + - name: api + namespace: nvcf + endpoints: + - api.nvcf.svc.cluster.local:8080/health + - name: nvct-api + namespace: nvcf + endpoints: + - nvct-api.nvcf.svc.cluster.local:8080/health + - name: notary-service + namespace: nvcf + endpoints: + - notary.nvcf.svc.cluster.local:8080/health + - name: grpc-proxy + namespace: nvcf + endpoints: + - grpc.nvcf.svc.cluster.local:10081/health + - name: ratelimiter + namespace: nvcf + endpoints: + - ratelimiter.nvcf.svc.cluster.local:8080/health + - name: llm-api-gateway + namespace: nvcf + endpoints: + - llm-api-gateway.nvcf.svc.cluster.local:8080/healthz + - llm-api-gateway.nvcf.svc.cluster.local:8080/readyz + - name: llm-request-router + namespace: nvcf + endpoints: + - llm-request-router-headless.nvcf.svc.cluster.local:8000/healthz + - llm-request-router-headless.nvcf.svc.cluster.local:8000/readyz + - name: reval + namespace: nvcf + endpoints: + - reval.nvcf.svc.cluster.local:8082/healthz + - name: invocation-service + namespace: nvcf + endpoints: + - invocation.nvcf.svc.cluster.local:8080/health + - name: sis + namespace: sis + endpoints: + - api.sis.svc.cluster.local:8080/health + - name: ess-api + namespace: ess + endpoints: + - ess-api.ess.svc.cluster.local:8080/health + - name: api-keys + namespace: api-keys + endpoints: + - api-keys.api-keys.svc.cluster.local:8080/health + - name: admin-issuer-proxy + namespace: api-keys + endpoints: + - admin-token-issuer-proxy.api-keys.svc.cluster.local:8080/healthz + - name: nats-auth-callout-service + namespace: nats-system + endpoints: + - nvcf-nats-auth-callout-service.nats-system.svc.cluster.local:8080/health + - name: nats + namespace: nats-system + endpoints: + - nats-headless.nats-system.svc.cluster.local:8222/healthz?js-server-only + - name: openbao-server + namespace: vault-system + endpoints: + - openbao-server.vault-system.svc.cluster.local:8200/v1/sys/health?standbyok=true + k8s_workloads: + - name: openbao-server-agent-injector + type: deployment + - name: cassandra + namespace: cassandra-system + k8s_workloads: + - name: cassandra + type: statefulset + - name: cert-manager + namespace: cert-manager + k8s_workloads: + - name: cert-manager + type: deployment + - name: cert-manager-cainjector + type: deployment + - name: cert-manager-webhook + type: deployment + - name: envoy-gateway + namespace: envoy-gateway-system + k8s_workloads: + - name: envoy-gateway + type: deployment + + # This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + extraVolumes: [] + # - name: foo + # secret: + # secretName: mysecret + # optional: false + + + extraVolumeMounts: [] + # - name: foo + # mountPath: "/etc/foo" + # readOnly: true + + nodeSelector: {} + + tolerations: [] + + affinity: {} diff --git a/src/uis/nvcf-ui/license-header.txt b/src/uis/nvcf-ui/license-header.txt new file mode 100644 index 000000000..2349ad443 --- /dev/null +++ b/src/uis/nvcf-ui/license-header.txt @@ -0,0 +1,14 @@ +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/uis/nvcf-ui/platforms/BUILD.bazel b/src/uis/nvcf-ui/platforms/BUILD.bazel new file mode 100644 index 000000000..3d51e50f1 --- /dev/null +++ b/src/uis/nvcf-ui/platforms/BUILD.bazel @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "sources", + srcs = glob(["*.bazel"]), +) + +platform( + name = "linux_arm64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +) + +platform( + name = "linux_x86_64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +) diff --git a/src/uis/nvcf-ui/rules/oci/BUILD.bazel b/src/uis/nvcf-ui/rules/oci/BUILD.bazel new file mode 100644 index 000000000..2096da0b1 --- /dev/null +++ b/src/uis/nvcf-ui/rules/oci/BUILD.bazel @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# OCI image helpers. +# A cacerts target can be added here later if using scratch/distroless bases. + +filegroup( + name = "sources", + srcs = glob(["*.bzl", "*.bazel"]) + ["//rules/oci/private:sources"], + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/rules/oci/defs.bzl b/src/uis/nvcf-ui/rules/oci/defs.bzl new file mode 100644 index 000000000..92058608d --- /dev/null +++ b/src/uis/nvcf-ui/rules/oci/defs.bzl @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"OCI image rules for packaging binaries into multi-arch containers." + +load("//rules/oci/private:go.bzl", _go_web_oci_image = "go_web_oci_image") + +# Packages Go binaries + a static web-asset directory into a multi-arch image. +go_web_oci_image = _go_web_oci_image diff --git a/src/uis/nvcf-ui/rules/oci/private/BUILD.bazel b/src/uis/nvcf-ui/rules/oci/private/BUILD.bazel new file mode 100644 index 000000000..e544b62c3 --- /dev/null +++ b/src/uis/nvcf-ui/rules/oci/private/BUILD.bazel @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Private OCI rule implementations. + +filegroup( + name = "sources", + srcs = glob(["*.bzl", "*.bazel"]), + visibility = ["//visibility:public"], +) diff --git a/src/uis/nvcf-ui/rules/oci/private/common.bzl b/src/uis/nvcf-ui/rules/oci/private/common.bzl new file mode 100644 index 000000000..1c41dd002 --- /dev/null +++ b/src/uis/nvcf-ui/rules/oci/private/common.bzl @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Shared helpers for OCI image rules." + +load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template") +load("@aspect_bazel_lib//lib:transitions.bzl", "platform_transition_filegroup") +load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index", "oci_load", "oci_push") +load("//rules/oci:transition.bzl", "multi_arch") + +# nvcf-ui pulls the distroless-static base (matches the Dockerfile). The +# umbrella defaults this to @ubuntu_noble, but that base is not pulled here. +DEFAULT_BASE = "@distroless_static" +DEFAULT_PLATFORMS = [ + "//platforms:linux_arm64", + "//platforms:linux_x86_64", +] + +COMMON_LAYERS = [] + +def create_oci_image( + name, + tars, + base, + entrypoint, + visibility, + registry = None, + tags = None, + repo_tag = None, + workdir = None): + """Creates OCI image targets with platform transitions and tarball output. + + Generates: + - {name}: Platform-transitioned OCI image (for local builds) + - {name}_index: Multi-arch image index (amd64 + arm64) + - {name}_load: Local docker load target + - {name}.tar: Tarball filegroup + - {name}_push: Push to registry (if registry is set) + """ + all_tags = ["manual"] + (tags or []) + + pre_transitioned = name + "_pre_transitioned" + oci_image( + name = pre_transitioned, + base = base, + tars = tars + COMMON_LAYERS, + entrypoint = entrypoint, + workdir = workdir, + visibility = ["//visibility:private"], + tags = all_tags, + ) + + platform_transition_filegroup( + name = name, + srcs = [pre_transitioned], + target_platform = select({ + "@platforms//cpu:arm64": "//platforms:linux_arm64", + "@platforms//cpu:x86_64": "//platforms:linux_x86_64", + }), + visibility = visibility, + tags = all_tags, + ) + + multi_arch_name = name + "_multi_arch" + multi_arch( + name = multi_arch_name, + image = pre_transitioned, + platforms = DEFAULT_PLATFORMS, + visibility = ["//visibility:private"], + tags = all_tags, + ) + + oci_image_index( + name = name + "_index", + images = [multi_arch_name], + visibility = visibility, + tags = all_tags, + ) + + # repo_tag defaults to ":latest"; callers at the repo root (empty + # package name) must pass an explicit repo_tag since ":latest" is invalid. + load_name = name + "_load" + oci_load( + name = load_name, + image = name, + repo_tags = [repo_tag or (native.package_name() + ":latest")], + visibility = visibility, + tags = all_tags, + ) + + native.filegroup( + name = name + ".tar", + srcs = [load_name], + output_group = "tarball", + visibility = visibility, + tags = all_tags, + ) + + if registry: + stamped_tags = name + "_stamped_tags" + expand_template( + name = stamped_tags, + out = name + "_tags.txt", + stamp_substitutions = { + "{VERSION}": "{{STABLE_VERSION}}", + }, + template = ["{VERSION}"], + visibility = ["//visibility:private"], + ) + + oci_push( + name = name + "_push", + image = name + "_index", + remote_tags = stamped_tags, + repository = registry, + visibility = visibility, + tags = all_tags, + ) diff --git a/src/uis/nvcf-ui/rules/oci/private/go.bzl b/src/uis/nvcf-ui/rules/oci/private/go.bzl new file mode 100644 index 000000000..0606cc2cf --- /dev/null +++ b/src/uis/nvcf-ui/rules/oci/private/go.bzl @@ -0,0 +1,149 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"OCI image rules for Go binaries." + +load("@rules_pkg//pkg:mappings.bzl", "strip_prefix") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("//rules/oci/private:common.bzl", "DEFAULT_BASE", "create_oci_image") + +# Re-exports an artifact built in the exec config, pinning the web-asset layer +# to the build host so the image's linux platform transition does not force a +# cross-rebuild of the native Vite/Rolldown toolchain (which cannot cross-run). +def _exec_files_impl(ctx): + return DefaultInfo(files = depset(ctx.files.src)) + +_exec_files = rule( + implementation = _exec_files_impl, + attrs = {"src": attr.label(cfg = "exec", allow_files = True)}, +) + +def _go_web_oci_image_impl(name, visibility, binaries, web_assets, web_dir, package_dir, base, entrypoint, registry, tags, repo_tag, source_layer): + tars = [] + + # One layer per binary at {package_dir}/{basename}, e.g. /app/server. + for binary in binaries: + blabel = native.package_relative_label(binary) + layer_name = name + "_" + blabel.name + "_layer" + pkg_tar( + name = layer_name, + srcs = [binary], + mode = "0755", + package_dir = package_dir, + strip_prefix = strip_prefix.from_pkg(""), + visibility = ["//visibility:private"], + ) + tars.append(layer_name) + + exec_web = name + "_web_exec" + _exec_files( + name = exec_web, + src = web_assets, + visibility = ["//visibility:private"], + ) + + # strip_prefix drops the tree artifact's short-path (e.g. "ui/ui") so its + # contents land directly under web_dir, not web_dir//. + web_layer = name + "_web_layer" + pkg_tar( + name = web_layer, + srcs = [exec_web], + package_dir = web_dir, + strip_prefix = strip_prefix.from_root( + web_assets.package + "/" + web_assets.name, + ), + visibility = ["//visibility:private"], + ) + tars.append(web_layer) + + # Optional pre-built source bundle layer (e.g. //:source_layer). + if source_layer: + tars.append(source_layer) + + entry = entrypoint + if not entry: + entry = [package_dir.rstrip("/") + "/" + native.package_relative_label(binaries[0]).name] + + create_oci_image( + name = name, + tars = tars, + base = base, + entrypoint = entry, + workdir = package_dir, + visibility = visibility, + registry = registry, + tags = tags, + repo_tag = repo_tag, + ) + +go_web_oci_image = macro( + doc = "Packages one or more go_binary targets plus a static web-asset " + + "directory into a single multi-arch OCI image. Used for the nvcf-ui " + + "server, which serves the built SPA from disk alongside the " + + "control-plane monitor binary.", + implementation = _go_web_oci_image_impl, + attrs = { + "binaries": attr.label_list( + doc = "go_binary targets to package under package_dir.", + mandatory = True, + configurable = False, + ), + "web_assets": attr.label( + doc = "Directory/filegroup of static web assets (Vite dist/).", + mandatory = True, + configurable = False, + ), + "web_dir": attr.string( + doc = "In-image mount path for web_assets.", + default = "/app/static", + configurable = False, + ), + "package_dir": attr.string( + doc = "In-image directory for the binaries.", + default = "/app", + configurable = False, + ), + "base": attr.label( + doc = "Base OCI image.", + default = DEFAULT_BASE, + configurable = False, + ), + "entrypoint": attr.string_list( + doc = "Container entrypoint. Defaults to {package_dir}/{first binary}.", + configurable = False, + ), + "registry": attr.string( + doc = "Registry to push to. If not set, push target is not created.", + configurable = False, + ), + "tags": attr.string_list( + doc = "Tags for generated targets. 'manual' is always added.", + configurable = False, + ), + "repo_tag": attr.string( + doc = "docker repo:tag for the _load target. Required at the repo " + + "root, where the package name is empty.", + default = "nvcf-ui:latest", + configurable = False, + ), + "source_layer": attr.label( + doc = "Optional pre-built pkg_tar target whose contents are added as " + + "a layer in the image. Intended to bundle the repository source " + + "at a well-known path (e.g. /usr/share/nvcf-ui/src) for " + + "open-source compliance and auditability.", + configurable = False, + ), + }, +) diff --git a/src/uis/nvcf-ui/rules/oci/transition.bzl b/src/uis/nvcf-ui/rules/oci/transition.bzl new file mode 100644 index 000000000..d36ab64bd --- /dev/null +++ b/src/uis/nvcf-ui/rules/oci/transition.bzl @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Multi-arch transition rule for OCI images." + +def _multiarch_transition(settings, attr): + return [ + {"//command_line_option:platforms": str(platform)} + for platform in attr.platforms + ] + +multiarch_transition = transition( + implementation = _multiarch_transition, + inputs = [], + outputs = ["//command_line_option:platforms"], +) + +def _multi_arch_impl(ctx): + return DefaultInfo(files = depset(ctx.files.image)) + +multi_arch = rule( + implementation = _multi_arch_impl, + attrs = { + "image": attr.label(cfg = multiarch_transition), + "platforms": attr.label_list(), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, +) diff --git a/src/uis/nvcf-ui/spec/control-plane-openapi.yaml b/src/uis/nvcf-ui/spec/control-plane-openapi.yaml new file mode 100644 index 000000000..fee2efcd7 --- /dev/null +++ b/src/uis/nvcf-ui/spec/control-plane-openapi.yaml @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +openapi: 3.1.0 +info: + title: NVCF UI Shim API + version: 0.1.0 + description: API for the NVCF UI shim service + license: + name: Apache-2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + +paths: + /v1/control-plane: + get: + operationId: getControlPlaneStatus + summary: Get control plane component health statuses + tags: + - Control Plane + security: [] + responses: + "200": + description: List of control plane component health statuses + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/ControlPlaneComponentStatus" + example: + - componentName: sis-api + namespace: sis + status: healthy + timestamp: "2026-06-27T10:00:00Z" + - componentName: ess-api + namespace: ess + status: unhealthy + timestamp: "2026-06-27T10:00:00Z" + +components: + schemas: + ControlPlaneComponentStatus: + type: object + description: Health status of a single control plane component + properties: + componentName: + type: string + description: Name of the control plane component + namespace: + type: string + description: Kubernetes namespace the component lives in + status: + type: string + description: Health status of the component + enum: + - healthy + - unhealthy + timestamp: + type: string + format: date-time + description: Time at which the status was recorded (RFC 3339 / ISO 8601) + required: + - componentName + - namespace + - status + - timestamp diff --git a/src/uis/nvcf-ui/spec/nvcf-openapi.yaml b/src/uis/nvcf-ui/spec/nvcf-openapi.yaml new file mode 100644 index 000000000..b59ad160b --- /dev/null +++ b/src/uis/nvcf-ui/spec/nvcf-openapi.yaml @@ -0,0 +1,985 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +openapi: 3.1.0 +info: + title: Cloud Functions + termsOfService: https://www.nvidia.com/en-us/legal_info + contact: + name: NVIDIA + url: https://www.nvidia.com/ + version: 2.245.7 +paths: + /v2/nvcf/accounts: + get: + tags: + - Account + summary: List all the NVIDIA Cloud Accounts onboarded with Cloud Functions + operationId: getCloudAccounts + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListAccountResponse' + /v2/nvcf/accounts/{ncaId}/functions: + get: + tags: + - Function Management + summary: List Functions + description: Lists all the functions associated with the specified NVIDIA Cloud Account. + operationId: getAllFunctions + parameters: + - name: ncaId + in: path + description: Id of the NVIDIA Cloud Account + required: true + schema: + type: string + format: UTF-8 + - name: visibility + in: query + description: "Query param 'visibility' indicates the kind of functions to be included\n in the response.\n" + required: false + schema: + type: array + default: + - authorized + - private + - public + items: + type: string + format: UTF-8 + enum: + - authorized + - private + - public + minItems: 0 + uniqueItems: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListFunctionsResponse' + /v2/nvcf/accounts/{ncaId}/functions/{functionId}/versions/{functionVersionId}: + get: + tags: + - Function Management + summary: Get Function Version Details + description: Retrieves detailed information of the specified function version in the specified NVIDIA Cloud Account + operationId: getFunctionVersion + parameters: + - name: ncaId + in: path + description: Id of the NVIDIA Cloud Account + required: true + schema: + type: string + format: UTF-8 + - name: functionId + in: path + description: Id of the function + required: true + schema: + type: string + format: uuid + maxLength: 36 + minLength: 36 + - name: functionVersionId + in: path + description: Version id of the function + required: true + schema: + type: string + format: uuid + maxLength: 36 + minLength: 36 + - name: includeSecrets + in: query + description: "Query param 'includeSecrets' indicates whether to include secret names for\n the function in the response.\n" + required: false + schema: + type: boolean + default: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/FunctionResponse' + /v2/nvcf/accounts/{ncaId}/deployments: + get: + tags: + - Function Deployment + summary: Get All Deployments + description: Retrieves deployments details of all the functions in the specified account + operationId: getAllFunctionDeployments + parameters: + - name: ncaId + in: path + description: Id of the NVIDIA Cloud Account + required: true + schema: + type: string + format: UTF-8 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListDeploymentsResponse' + /v2/nvcf/accounts/{ncaId}/deployments/functions/{functionId}/versions/{functionVersionId}: + get: + tags: + - Function Deployment + summary: Get Function Deployment Details + description: Retrieves deployment details of the specified function + operationId: getFunctionDeployment + parameters: + - name: ncaId + in: path + description: Id of the NVIDIA Cloud Account + required: true + schema: + type: string + format: UTF-8 + - name: functionId + in: path + description: Function id + required: true + schema: + type: string + format: uuid + maxLength: 36 + minLength: 36 + - name: functionVersionId + in: path + description: Function version id + required: true + schema: + type: string + format: uuid + maxLength: 36 + minLength: 36 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DeploymentResponse' +components: + schemas: + RateLimitDto: + type: object + description: Data Transfer Object(DTO) representing Rate limit config + properties: + rateLimit: + type: string + format: UTF-8 + description: Rate + pattern: ^(?!.*-([SMHD]).*-\1)[1-9]\d*-[SMHD](,\s*[1-9]\d*-[SMHD])*$ + exemptedNcaIds: + type: array + description: NCA ID Exemptions + items: + type: string + maxItems: 32 + minItems: 0 + uniqueItems: true + perNcaIdRate: + type: object + additionalProperties: + type: string + description: Per NCA ID Rate + syncCheck: + type: boolean + description: Sync check. Defaults to false + perUserRate: + type: string + format: UTF-8 + description: Per-user rate, intended for traffic authenticated with an invocation delegation token (InvocationToken). When set, this single rate is enforced individually against every unique caller identity, in addition to the NCA-tier limit. + pattern: ^(?!.*-([SMHD]).*-\1)[1-9]\d*-[SMHD](,\s*[1-9]\d*-[SMHD])*$ + ArtifactDto: + type: object + description: Data Transfer Object(DTO) representing an artifact + properties: + name: + type: string + format: UTF-8 + description: Artifact name + version: + type: string + format: UTF-8 + description: Artifact version + uri: + type: string + format: uri + description: Artifact URI + required: + - name + - uri + - version + ContainerEnvironmentEntryDto: + type: object + description: Data Transfer Object(DTO) representing a container environment entry + properties: + key: + type: string + format: UTF-8 + description: Container environment key + value: + type: string + format: UTF-8 + description: Container environment value + required: + - key + - value + FunctionDto: + type: object + description: Data Transfer Object (DTO) representing a function + properties: + id: + type: string + format: uuid + description: Unique function id + maxLength: 36 + minLength: 36 + ncaId: + type: string + format: UTF-8 + description: NVIDIA Cloud Account Id + versionId: + type: string + format: uuid + description: Unique function version id + maxLength: 36 + minLength: 36 + name: + type: string + format: UTF-8 + description: Function name + status: + type: string + format: UTF-8 + description: Function status + enum: + - ACTIVE + - DEPLOYING + - ERROR + - INACTIVE + - DELETED + - DEGRADED + - DEGRADING + inferenceUrl: + type: string + format: uri + description: Entrypoint for invoking the container to process requests + ownedByDifferentAccount: + type: boolean + description: "Indicates whether the function is owned by another account. If the account\n that is being used to lookup functions happens to be authorized to invoke/list\n this function which is owned by a different account, then this field is set\n to true and ncaId will contain the id of the account that owns the function.\n Otherwise, this field is not set as it defaults to false.\n" + inferencePort: + type: integer + format: int32 + description: Optional port number where the inference listener is running - defaults to 8000 for Triton + containerArgs: + type: string + format: UTF-8 + description: Args used to launch the container + containerEnvironment: + type: array + description: Environment settings used to launch the container + items: + $ref: '#/components/schemas/ContainerEnvironmentEntryDto' + minItems: 0 + models: + type: array + description: Optional list of models + items: + $ref: '#/components/schemas/FunctionModelDto' + minItems: 0 + containerImage: + type: string + format: uri + description: Optional custom container + apiBodyFormat: + type: string + format: UTF-8 + description: Invocation request body format + enum: + - PREDICT_V2 + - CUSTOM + helmChart: + type: string + format: uri + description: Optional Helm Chart + helmChartServiceName: + type: string + format: UTF-8 + description: 'Helm Chart Service Name specified only when helmChart property is specified + + ' + healthUri: + type: string + format: uri + deprecated: true + description: Health endpoint for the container or helmChart. Deprecated, use health.uri instead. + createdAt: + type: string + format: date-time + description: Function creation timestamp + activeInstances: + type: array + description: List of active instances for this function. + items: + $ref: '#/components/schemas/InstanceDto' + minItems: 0 + resources: + type: array + description: Optional set of resources. + items: + $ref: '#/components/schemas/ArtifactDto' + minItems: 0 + uniqueItems: true + tags: + type: array + description: Optional set of tags. Maximum allowed number of tags per function is 64. Maximum length of each tag is 128 chars. + items: + type: string + minItems: 0 + uniqueItems: true + description: + type: string + format: UTF-8 + description: Function/version description + health: + $ref: '#/components/schemas/HealthDto' + description: Function health configuration + functionType: + type: string + format: UTF-8 + description: Used to indicate a STREAMING function. Defaults to DEFAULT. + enum: + - DEFAULT + - STREAMING + - LLM + secrets: + type: array + description: Optional secret names + items: + type: string + minItems: 0 + uniqueItems: true + rateLimit: + $ref: '#/components/schemas/RateLimitDto' + description: Optional rate limit policy + telemetries: + $ref: '#/components/schemas/TelemetriesDto' + description: Optional telemetry configuration for the function + required: + - createdAt + - functionType + - healthUri + - id + - name + - ncaId + - status + - versionId + FunctionModelDto: + type: object + description: Model associated with a function, including optional LLM routing metadata + properties: + name: + type: string + format: UTF-8 + description: Model name + version: + type: string + format: UTF-8 + description: Model version. Optional for LLM-type functions. + uri: + type: string + format: uri + description: Model URI. Optional for LLM-type functions. + llmConfig: + $ref: '#/components/schemas/LlmConfigDto' + description: LLM-specific configuration for this model. Only relevant for LLM-type functions. + required: + - name + FunctionResponse: + type: object + description: Response body with function details + properties: + function: + $ref: '#/components/schemas/FunctionDto' + description: Function details + required: + - function + HealthDto: + type: object + description: Data Transfer Object(DTO) representing a function ne + properties: + protocol: + type: string + format: UTF-8 + description: HTTP/gPRC protocol type for health endpoint + enum: + - HTTP + - gRPC + uri: + type: string + format: uri + description: Health endpoint for the container or the helmChart + port: + type: integer + format: int32 + description: Port number where the health listener is running + timeout: + type: string + format: duration + description: ISO 8601 duration string in PnDTnHnMn.nS format + example: PT10S + expectedStatusCode: + type: integer + format: int32 + description: Expected return status code considered as successful. + required: + - expectedStatusCode + - port + - protocol + - timeout + - uri + InstanceDto: + type: object + description: Data Transfer Object(DTO) representing an instance + properties: + instanceId: + type: string + format: UTF-8 + description: Unique id of the instance + functionId: + type: string + format: uuid + description: Function executing on the instance + maxLength: 36 + minLength: 36 + functionVersionId: + type: string + format: uuid + description: Function version executing on the instance + maxLength: 36 + minLength: 36 + instanceType: + type: string + format: UTF-8 + description: GPU instance-type powering the instance + instanceStatus: + type: string + format: UTF-8 + description: Instance status + enum: + - ACTIVE + - STARTING + - RUNNING + - ERRORED + - PREEMPTED + - DELETED + - TERMINATED + icmsRequestId: + type: string + format: uuid + description: ICMS request-id used to launch this instance + maxLength: 36 + minLength: 36 + ncaId: + type: string + format: UTF-8 + description: NVIDIA Cloud Account Id that owns the function running on the instance + gpu: + type: string + format: UTF-8 + description: GPU name powering the instance + backend: + type: string + format: UTF-8 + description: Backend where the instance is running + location: + type: string + format: UTF-8 + description: Location such as zone name or region where the instance is running + instanceCreatedAt: + type: string + format: date-time + description: Instance creation timestamp + instanceUpdatedAt: + type: string + format: date-time + description: Instance's last updated timestamp + LlmConfigDto: + type: object + description: LLM-specific configuration for a model + properties: + uris: + type: array + description: OpenAI-compatible URIs supported by this model (e.g. /v1/chat/completions). Required for LLM models. + items: + type: string + minItems: 1 + tokenRateLimit: + type: string + format: UTF-8 + description: Token-level rate limit for this model. + tokenizer: + type: string + format: UTF-8 + description: Tokenizer identifier for this model. + routingMethod: + type: string + format: UTF-8 + description: Routing method for this model. + required: + - uris + TelemetriesDto: + type: object + description: Telemetry configuration for logs, metrics, and traces. + properties: + logsTelemetryId: + type: string + format: uuid + description: UUID representing the logs telemetry. + maxLength: 36 + minLength: 36 + metricsTelemetryId: + type: string + format: uuid + description: UUID representing the metrics telemetry. + maxLength: 36 + minLength: 36 + tracesTelemetryId: + type: string + format: uuid + description: UUID representing the traces telemetry. + maxLength: 36 + minLength: 36 + AutoscalingConfigurationDto: + type: object + description: Data Transfer Object(DTO) representing autoscaling configuration + properties: + scaleUpDetails: + $ref: '#/components/schemas/ScalingDetails' + description: Configuration for scaling up + scaleDownDetails: + $ref: '#/components/schemas/ScalingDetails' + description: Configuration for scaling down + DeploymentHealthDto: + type: object + description: Data Transfer Object(DTO) representing deployment error + properties: + icmsRequestId: + type: string + format: uuid + description: ICMS Request ID + maxLength: 36 + minLength: 36 + gpu: + type: string + format: UTF-8 + description: GPU Type as per SDD + minLength: 1 + backend: + type: string + format: UTF-8 + description: Backend/CSP where the GPU powered instance will be launched + minLength: 1 + instanceType: + type: string + format: UTF-8 + description: Instance type + error: + type: string + format: UTF-8 + description: Deployment error + minLength: 1 + required: + - backend + - error + - gpu + - icmsRequestId + DeploymentResponse: + type: object + description: Function Deployment Response + properties: + deployment: + $ref: '#/components/schemas/FunctionDeploymentDto' + description: Deployment details + required: + - deployment + FunctionDeploymentDto: + type: object + description: Function deployment response + properties: + functionId: + type: string + format: uuid + description: Function id + maxLength: 36 + minLength: 36 + functionVersionId: + type: string + format: uuid + description: Function version id + maxLength: 36 + minLength: 36 + deploymentId: + type: string + format: uuid + description: Last deployment id + maxLength: 36 + minLength: 36 + functionName: + type: string + format: UTF-8 + description: Function name + minLength: 1 + ncaId: + type: string + format: UTF-8 + description: NVIDIA Cloud Account Id + minLength: 1 + functionStatus: + type: string + format: UTF-8 + description: Function status + enum: + - ACTIVE + - DEPLOYING + - ERROR + - INACTIVE + - DELETED + - DEGRADED + - DEGRADING + healthInfo: + type: array + description: "Health info for a deployment specification is included only if there are any\n issues/errors.\n" + items: + $ref: '#/components/schemas/DeploymentHealthDto' + minItems: 0 + deploymentSpecifications: + type: array + description: Function deployment details + items: + $ref: '#/components/schemas/GpuSpecificationDto' + minItems: 0 + createdAt: + type: string + format: date-time + description: Function deployment creation timestamp + lastUpdatedAt: + type: string + format: date-time + description: Function deployment modification timestamp + required: + - createdAt + - deploymentId + - deploymentSpecifications + - functionId + - functionName + - functionStatus + - functionVersionId + - lastUpdatedAt + - ncaId + GpuSpecificationDto: + type: object + description: Data Transfer Object(DTO) representing GPU specification. + properties: + gpuSpecificationId: + type: string + format: uuid + description: GPU specification id + maxLength: 36 + minLength: 36 + gpu: + type: string + format: UTF-8 + description: GPU name from the cluster + minLength: 1 + backend: + type: string + format: UTF-8 + description: Use clusters instead of this property. Legacy property typically used when deploying a function to GFN. + maxInstances: + type: integer + format: int32 + description: Maximum number of instances for the deployment + minInstances: + type: integer + format: int32 + description: Minimum number of instances for the deployment + instanceType: + type: string + format: UTF-8 + description: Instance type, based on GPU, assigned to a Worker + minLength: 1 + availabilityZones: + type: array + description: List of availability-zones(or clusters) in the cluster group + items: + type: string + minItems: 0 + maxRequestConcurrency: + type: integer + format: int32 + description: Max request concurrency between 1 (default) and 16384. + maximum: 16384 + minimum: 1 + configuration: + type: object + description: "Typically used when the function is based on Helm Charts to substitute\n placeholders in values yaml.\n" + clusters: + type: array + description: 'Specific clusters powered by the selected instance-type to deploy function. + + ' + items: + type: string + minItems: 0 + uniqueItems: true + regions: + type: array + description: 'List of regions allowed to deploy. The instance or worker node will be in one of + + the specified geographical regions. + + ' + items: + type: string + minItems: 0 + uniqueItems: true + attributes: + type: array + description: 'Specific cluster attributes/capabilities to deploy functions. For example, + + HIPPA Compliant, Confidential Compute Compliant, etc. + + ' + items: + type: string + minItems: 0 + uniqueItems: true + cpuArch: + type: string + format: UTF-8 + description: CPU Architecture details + os: + type: string + format: UTF-8 + description: Operating System details + driverVersion: + type: string + format: UTF-8 + description: GPU driver version + storage: + type: string + format: UTF-8 + description: Amount of available storage, e.g. 80G + systemMemory: + type: string + format: UTF-8 + description: Amount of RAM + gpuMemory: + type: string + format: UTF-8 + description: Amount of GPU memory + autoscalingConfiguration: + $ref: '#/components/schemas/AutoscalingConfigurationDto' + description: Customizable autoscaling configuration + helmValidationPolicy: + $ref: '#/components/schemas/HelmValidationPolicyDto' + description: Helm validation policy + required: + - gpu + - instanceType + - maxInstances + - minInstances + HelmValidationPolicyDto: + type: object + description: Data Transfer Object(DTO) representing Helm validation policy + properties: + name: + type: string + format: UTF-8 + description: Helm validation policy name. + enum: + - Default + - Unrestricted + extraKubernetesTypes: + type: array + description: 'An API Group in Kubernetes is a collection of related functionality. + + When present, must contain at least one entry; each entry must be a valid + + KubernetesType (group, version, kind). + + ' + items: + $ref: '#/components/schemas/KubernetesType' + minItems: 1 + required: + - name + KubernetesType: + type: object + properties: + group: + type: string + format: UTF-8 + description: Name of API Group + minLength: 1 + version: + type: string + format: UTF-8 + description: Version of API Group + minLength: 1 + kind: + type: string + format: UTF-8 + description: API Group resource or Kind + minLength: 1 + required: + - group + - kind + - version + ScalingDetails: + type: object + properties: + metric: + type: string + format: UTF-8 + description: Scaling metric + factor: + type: number + format: float + description: Scaling factor must be greater-than 1.0 for scale up and less-than 1.0 for scale down. This factor is used to multiply the currentinstance count when specified threshold is met. + threshold: + type: integer + format: int32 + description: Scaling threshold (0-100) as a percentage of utilizationupon which the number of current instances are multiplied with thespecified factor. + stickiness: + $ref: '#/components/schemas/StickinessWindow' + description: Stickiness window configuration + required: + - factor + - threshold + StickinessWindow: + type: object + properties: + size: + type: string + format: duration + description: Window size must be less than or equal to PT1H and will berounded to the nearest minute + example: PT30M + threshold: + type: string + format: duration + description: Window threshold must be less than size + example: PT5M + required: + - size + - threshold + AccountDto: + type: object + description: Data Transfer Object(DTO) representing an account + properties: + ncaId: + type: string + format: UTF-8 + description: NVIDIA Cloud Account id + adminClientIds: + type: array + description: Client Ids associated with the NVIDIA Cloud Account + items: + type: string + minItems: 0 + name: + type: string + format: UTF-8 + description: Account/customer name + maxFunctionsAllowed: + type: integer + format: int32 + description: Maximum number of functions allowed for Account + maxTasksAllowed: + type: integer + format: int32 + description: Maximum number of tasks allowed for Account + maxTelemetriesAllowed: + type: integer + format: int32 + description: Maximum number of telemetries allowed for Account + maxRegistryCredentialsAllowed: + type: integer + format: int32 + description: Maximum number of registry credentials allowed for Account + lastUpdatedAt: + type: string + format: date-time + description: Last time the account was updated. + required: + - maxFunctionsAllowed + - maxRegistryCredentialsAllowed + - maxTasksAllowed + - maxTelemetriesAllowed + - name + - ncaId + ListFunctionsResponse: + type: object + description: Response body containing list of functions + properties: + functions: + type: array + description: List of functions + items: + $ref: '#/components/schemas/FunctionDto' + minItems: 0 + required: + - functions + ListAccountResponse: + type: object + description: Response body containing list of NVIDIA Cloud Accounts + properties: + cloudAccounts: + type: array + description: List of NVIDIA Cloud Accounts + items: + $ref: '#/components/schemas/AccountDto' + minItems: 0 + required: + - cloudAccounts + ListDeploymentsResponse: + type: object + description: Function Deployments Response + properties: + deployments: + type: array + description: List of deployments + items: + $ref: '#/components/schemas/FunctionDeploymentDto' + minItems: 0 + required: + - deployments diff --git a/src/uis/nvcf-ui/spec/sis-openapi.yaml b/src/uis/nvcf-ui/spec/sis-openapi.yaml new file mode 100644 index 000000000..2a4d88640 --- /dev/null +++ b/src/uis/nvcf-ui/spec/sis-openapi.yaml @@ -0,0 +1,366 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +openapi: 3.1.0 +info: + title: Gfn Spot + termsOfService: https://www.nvidia.com/en-us/legal_info + contact: + name: NVIDIA + url: https://www.nvidia.com/ + version: "1.0" +paths: + /v1/si/accounts/{ncaId}/clusters: + get: + tags: + - Clusters + summary: Get NVCA Clusters + description: API to get the cluster details for given NcaId + operationId: getClusters + parameters: + - name: ncaId + in: path + description: NVIDIA Cloud Account ID + required: true + schema: + type: string + format: UTF-8 + description: NVIDIA Cloud Account ID + - name: includeAuthorizedClusters + in: query + description: Set this param to include all clusters authorized to given ncaId + required: false + schema: + type: boolean + description: Set this param to include all clusters authorized to given ncaId + - name: includeGfnInAuthorizedClusters + in: query + description: If includeAuthorizedClusters=true then this param will be considered else ignored + required: false + schema: + type: boolean + description: If includeAuthorizedClusters=true then this param will be considered else ignored + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/GetClusterResponse" + minItems: 0 + "400": + description: Bad request + "401": + description: Unauthorized + "403": + description: Forbidden - invalid token provided + "404": + description: Cluster not found + "500": + description: Internal server error + /v1/si/accounts/{ncaId}/clusters/{clusterId}: + get: + tags: + - Clusters + summary: Get NVCA Cluster + description: API to get the cluster details for ncaId and clusterId + operationId: getClusterForNcaIdAndClusterId + parameters: + - name: ncaId + in: path + description: NVIDIA Cloud Account ID + required: true + schema: + type: string + format: UTF-8 + description: NVIDIA Cloud Account ID + - name: clusterId + in: path + description: Cluster ID + required: true + schema: + type: string + format: UTF-8 + description: Cluster ID + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/GetClusterResponse" + "400": + description: Bad request + "401": + description: Unauthorized + "403": + description: Forbidden - invalid token provided + "404": + description: Cluster not found + "500": + description: Internal server error +components: + schemas: + GetClusterResponse: + type: object + description: Get Cluster Response + properties: + clusterName: + type: string + description: Name of the cluster + clusterGroupName: + type: string + description: Group to which the custer belongs + clusterDescription: + type: string + description: Description of the cluster + ncaId: + type: string + description: NVIDIA Cloud Account ID + authorizedNCAIds: + type: array + description: AuthorizedNcaIds for the cluster + items: + type: string + uniqueItems: true + cloudProvider: + type: string + description: Cloud Provider for the cluster + enum: + - ON-PREM + - DGX-CLOUD + - AWS + - OCI + - AZURE + - GDN + - GFN + - GCP + - NCP + region: + type: string + description: Region of the cluster + capabilities: + type: array + description: Capabilities of the cluster + items: + type: string + uniqueItems: true + attributes: + type: array + description: Attributes related to the cluster + items: + type: string + uniqueItems: true + customAttributes: + type: array + description: Custom attributes related to the cluster + items: + type: string + uniqueItems: true + gpus: + type: array + description: GPUs supported in cluster + items: + $ref: "#/components/schemas/GpuResponseSchema" + uniqueItems: true + nvcaVersion: + type: string + description: Nvca version + clusterId: + type: string + description: Cluster ID + clusterGroupId: + type: string + description: Cluster Group ID + status: + type: string + description: Cluster status + enum: + - READY + - PAUSED + - FAILED + - ABANDONED + - NOT_READY + - UNHEALTHY + - DELETED + - CORDON + - CORDON_AND_DRAIN + clusterSource: + type: string + description: Cluster Source + nvcaLastConnected: + type: string + format: date-time + description: NVCA last connected timestamp + k8sVersion: + type: string + description: K8s version on cluster + gpuUsage: + type: object + additionalProperties: + $ref: "#/components/schemas/GpuCapacity" + description: Gpu usage shared by cluster + sisConfig: + $ref: "#/components/schemas/SisConfig" + description: Sis Config + vaultConfig: + $ref: "#/components/schemas/VaultConfig" + description: Vault Config + imageCredentialHelper: + $ref: "#/components/schemas/ImageCredentialHelper" + description: Image Credential Helper Config + clusterUpgradeStatus: + type: string + description: Cluster upgrade status + enum: + - IN_PROGRESS + - AVAILABLE + - PAUSED + - SUCCESS + - FAILED + clusterKeyId: + type: string + description: Cluster key Id + clusterConfigurations: + type: object + additionalProperties: + type: string + description: Advanced BYOC cluster configurations as key-value string map + clusterConfigurationFiles: + type: object + additionalProperties: + type: string + description: Advanced BYOC cluster configuration files as base64-encoded string map + GpuCapacity: + type: object + properties: + capacity: + type: integer + format: int32 + allocated: + type: integer + format: int32 + available: + type: integer + format: int32 + GpuResponseSchema: + type: object + properties: + name: + type: string + description: Name of the gpu + minLength: 1 + capacity: + type: integer + format: int32 + description: Total gpu capacity + instanceTypes: + type: array + description: List of instance types for that Gpu + items: + $ref: "#/components/schemas/InstanceTypeResponseSchema" + uniqueItems: true + required: + - name + ImageConfig: + type: object + description: ImageConfig + properties: + repository: + type: string + description: Image repository + minLength: 1 + tag: + type: string + description: Image tag + minLength: 1 + required: + - repository + - tag + ImageCredentialHelper: + type: object + description: ImageCredentialHelper + properties: + imageConfig: + $ref: "#/components/schemas/ImageConfig" + description: Image config for credential helper + InstanceTypeResponseSchema: + type: object + properties: + cpuCores: + type: integer + format: int32 + description: Number of cpu cores for the instance type + systemMemory: + type: string + description: Memory of the System. e.g. 24G + gpuMemory: + type: string + description: Memory of the GPU. e.g. 24G + gpuCount: + type: integer + format: int32 + description: Number of GPUs to be attached to the worker pod. Supported values 1, 2, 4, 8 (Default value is 1) + name: + type: string + description: Name of the instance type + minLength: 1 + description: + type: string + description: Description of the instance type + value: + type: string + description: Value of the instance type + minLength: 1 + cpuArch: + type: string + description: Architecture details of the CPU + os: + type: string + description: Operating system details + driverVersion: + type: string + description: GPU driver version + storage: + type: string + description: The amount of available storage, e.g. 80G + default: + type: boolean + description: Indicates if its the default instance type for the GPU + required: + - name + - value + SisConfig: + type: object + description: SisConfig + properties: + publicKeysetEndpoint: + type: string + description: Public Keyset Endpoint + tokenURL: + type: string + description: Token Endpoint URL + spotServiceURL: + type: string + description: Spot Instance Service Endpoint URL + VaultConfig: + type: object + description: VaultConfig + properties: + address: + type: string + description: Vault address diff --git a/src/uis/nvcf-ui/tools/workspace_status.sh b/src/uis/nvcf-ui/tools/workspace_status.sh new file mode 100755 index 000000000..f96c8334d --- /dev/null +++ b/src/uis/nvcf-ui/tools/workspace_status.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Emits STABLE_* keys consumed by --stamp-aware rules (OCI tag templates in +# rules/oci and go x_defs). Mirrors the nvcf/nvcf umbrella script so the +# stamping contract is identical after migration. + +set -euo pipefail + +COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") +DIRTY="" +if [ "$COMMIT" != "unknown" ] && [ -n "$(git status --porcelain --untracked-files=no 2>/dev/null)" ]; then + DIRTY="-dirty" +fi + +# CI overrides for VERSION (from git tag or MR sha) and BUILD_USER. +if [ -n "${NVCF_VERSION:-}" ]; then + VERSION="${NVCF_VERSION}" +elif TAG=$(git describe --tags --exact-match HEAD 2>/dev/null); then + VERSION="${TAG}" +else + VERSION="mr-${COMMIT}" +fi + +BUILD_USER="${NVCF_BUILD_USER:-$(whoami 2>/dev/null || echo 'unknown')}" +BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +GO_VERSION="${NVCF_GO_VERSION:-bazel-rules_go}" + +echo "STABLE_VERSION ${VERSION}" +echo "STABLE_GIT_COMMIT ${COMMIT}${DIRTY}" +echo "STABLE_GIT_BRANCH ${BRANCH}" +echo "STABLE_BUILD_USER ${BUILD_USER}" +echo "STABLE_GO_VERSION ${GO_VERSION}" +echo "STABLE_OCI_TAG ${VERSION}-${COMMIT}${DIRTY}" + +# Volatile key (no STABLE_ prefix): injected into stamped binaries but its +# changes do NOT invalidate the action cache, so --stamp reuses cached links. +echo "BUILD_DATE ${BUILD_DATE}" diff --git a/src/uis/nvcf-ui/ui/.gitignore b/src/uis/nvcf-ui/ui/.gitignore new file mode 100644 index 000000000..72f8c4853 --- /dev/null +++ b/src/uis/nvcf-ui/ui/.gitignore @@ -0,0 +1,15 @@ +node_modules +.DS_Store +coverage +bundle-report.html +dist +dist-ssr +*.local +.env +.nitro +.tanstack +.wrangler +.output +.vinxi +__unconfig* +todos.json diff --git a/src/uis/nvcf-ui/ui/BUILD.bazel b/src/uis/nvcf-ui/ui/BUILD.bazel new file mode 100644 index 000000000..92ad05821 --- /dev/null +++ b/src/uis/nvcf-ui/ui/BUILD.bazel @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@npm//:defs.bzl", "npm_link_all_packages") +load("@npm//ui:vite/package_json.bzl", vite_bin = "bin") +load("@npm//ui:vitest/package_json.bzl", vitest_bin = "bin") + +exports_files(["pnpm-lock.yaml"]) + +npm_link_all_packages(name = "node_modules") + +SRCS = glob( + [ + "src/**", + "public/**", + "vendor/**", + ], + allow_empty = True, +) + [ + "index.html", + "vite.config.ts", + "tsconfig.json", + "package.json", +] + +filegroup( + name = "sources", + srcs = SRCS, + visibility = ["//visibility:public"], +) + +# Vite production build (out_dir "ui" avoids colliding with the source ui/dist/). +# no-sandbox is required for Vite 8's Rolldown bundler (aspect-build/rules_js#2888); +# do not remove it until that issue is resolved. +vite_bin.vite( + name = "ui", + srcs = SRCS + [":node_modules"], + args = [ + "build", + "--outDir", + "ui", + ], + chdir = package_name(), + out_dirs = ["ui"], + tags = ["no-sandbox"], + visibility = ["//visibility:public"], +) + +# Vitest suite. no-sandbox for the same Vite 8/Rolldown reason as //ui; +# BAZEL_VITEST enables the runfiles overrides in vite.config.ts. +vitest_bin.vitest_test( + name = "test", + args = ["run"], + chdir = package_name(), + data = SRCS + [ + "vitest.setup.ts", + ":node_modules", + ], + env = {"BAZEL_VITEST": "1"}, + tags = ["no-sandbox"], +) diff --git a/src/uis/nvcf-ui/ui/Taskfile.yml b/src/uis/nvcf-ui/ui/Taskfile.yml new file mode 100644 index 000000000..b63a44feb --- /dev/null +++ b/src/uis/nvcf-ui/ui/Taskfile.yml @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: "3" + +tasks: + install: + desc: Install UI dependencies + cmds: + - pnpm install + + generate: + desc: Build OpenAPI spec and generate typed query hooks, schemas, and mocks + cmds: + - pnpm generate + + dev: + desc: Start Vite dev server + cmds: + - pnpm dev + env: + VITE_MOCK: "{{.MOCK}}" + VITE_SCENARIO: "{{.SCENARIO}}" + + build: + desc: Production build + cmds: + - pnpm build {{if .OUT_DIR}}--outDir {{.OUT_DIR}}{{end}} + + lint: + desc: Lint and format check + cmds: + - pnpm check + + lint:fix: + desc: Auto-fix lint and format issues + cmds: + - pnpm check:fix + + typecheck: + desc: Run TypeScript type checking + cmds: + - pnpm tsc --noEmit + + test: + desc: Run tests + cmds: + - pnpm test + + vendor-css: + desc: Fetch KUI foundations CSS from CDN (run after bumping @nvidia/foundations-react-core) + cmds: + - pnpm vendor-css diff --git a/src/uis/nvcf-ui/ui/biome.json b/src/uis/nvcf-ui/ui/biome.json new file mode 100644 index 000000000..251591caf --- /dev/null +++ b/src/uis/nvcf-ui/ui/biome.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.5/schema.json", + "root": false, + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**/src/**/*", + "**/index.html", + "**/vite.config.ts", + "**/orval.config.ts", + "**/orval-transformer.ts", + "!**/src/styles.css" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 2 + }, + "assist": { "actions": { "source": { "organizeImports": "on", "useSortedAttributes": "on" } } }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": { + "options": { + "ignoreRestSiblings": true + }, + "level": "error" + }, + "noUnusedImports": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "indentWidth": 2 + } + }, + "overrides": [ + { + "includes": ["**/src/generated/**"], + "linter": { "enabled": false } + } + ] +} diff --git a/src/uis/nvcf-ui/ui/index.html b/src/uis/nvcf-ui/ui/index.html new file mode 100644 index 000000000..899781b41 --- /dev/null +++ b/src/uis/nvcf-ui/ui/index.html @@ -0,0 +1,29 @@ + + + + + + + + + + +
+ + + diff --git a/src/uis/nvcf-ui/ui/orval.config.ts b/src/uis/nvcf-ui/ui/orval.config.ts new file mode 100644 index 000000000..9a2217f69 --- /dev/null +++ b/src/uis/nvcf-ui/ui/orval.config.ts @@ -0,0 +1,369 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { faker } from "@faker-js/faker"; +import { defineConfig, type OutputOptions } from "orval"; + +const sharedOutput: OutputOptions = { + baseUrl: "", + mode: "tags-split", + target: "./src/generated/api", + schemas: { + path: "./src/generated/model", + type: "typescript", + }, + mock: { + baseUrl: "", + type: "msw", + locale: "en", + useExamples: true, + }, + client: "react-query", + httpClient: "fetch", + indexFiles: false, + formatter: "biome", + override: { + mutator: { + path: "./src/lib/fetch.ts", + name: "customFetch", + }, + fetch: { + includeHttpResponseReturnType: false, + }, + mock: { + delay: 150, + arrayMin: 1, + arrayMax: 25, + numberMin: 0, + numberMax: 100, + }, + query: { + useSuspenseQuery: true, + }, + }, +}; + +export default defineConfig({ + "control-plane": { + input: { + target: "../spec/control-plane-openapi.yaml", + }, + output: { ...sharedOutput, clean: true }, + }, + nvcf: { + input: { + target: "../spec/nvcf-openapi.yaml", + }, + output: { + ...sharedOutput, + override: { + ...sharedOutput.override, + operations: { + getCloudAccounts: { + query: { + options: { + staleTime: Number.POSITIVE_INFINITY, + }, + }, + }, + }, + tags: { + Account: { + mock: { + properties: { + "/cloudAccounts/": () => + Array.from( + { length: faker.number.int({ min: 1, max: 3 }) }, + () => ({ + ncaId: faker.string.alphanumeric({ length: 13 }), + name: faker.company.name(), + maxFunctionsAllowed: faker.number.int(), + maxTasksAllowed: faker.number.int(), + maxTelemetriesAllowed: faker.number.int(), + maxRegistryCredentialsAllowed: faker.number.int(), + }), + ), + }, + }, + }, + "Function Management": { + mock: { + properties: { + "/name/": () => faker.lorem.slug(), + "/tags/": () => + faker.helpers.arrayElements(["inference", "gpu", "llm"], { + min: 0, + max: 3, + }), + "/containerImage/": () => + `nvcr.io/nvidia/${faker.lorem.slug()}:${faker.system.semver()}`, + "/helmChart$/": () => + `helm.ngc.nvidia.com/nvidia/${faker.lorem.slug()}`, + "/inferenceUrl/": () => + `https://api.nvcf.nvidia.com/v2/nvcf/pexec/functions/${faker.string.uuid()}`, + "/health\\.uri/": () => + faker.helpers.arrayElement([ + "health/ready", + "healthz", + "v1/health/ready", + ]), + "/uri/": () => + `api.ngc.nvidia.com/v2/org/nvidia/models/${faker.lorem.slug()}/versions/${faker.system.semver()}`, + "/containerEnvironment/": () => + faker.helpers.maybe( + () => + Array.from( + { length: faker.number.int({ min: 1, max: 3 }) }, + () => ({ + key: `${faker.lorem.word().toUpperCase()}_${faker.lorem.word().toUpperCase()}`, + value: faker.string.alphanumeric(16), + }), + ), + { probability: 0.7 }, + ), + "/models/": () => + faker.helpers.maybe( + () => + Array.from( + { length: faker.number.int({ min: 1, max: 3 }) }, + () => ({ + name: faker.lorem.slug(), + uri: `api.ngc.nvidia.com/v2/org/nvidia/models/${faker.lorem.slug()}/versions/${faker.system.semver()}`, + }), + ), + { probability: 0.7 }, + ), + "/resources/": () => + faker.helpers.maybe( + () => + Array.from( + { length: faker.number.int({ min: 1, max: 3 }) }, + () => ({ + name: faker.lorem.slug(), + version: faker.system.semver(), + uri: `api.ngc.nvidia.com/v2/org/nvidia/resources/${faker.lorem.slug()}/versions/${faker.system.semver()}`, + }), + ), + { probability: 0.7 }, + ), + }, + }, + }, + "Function Deployment": { + mock: { + properties: { + // Build the whole spec array so instance types are unique and + // each `gpu` matches its instanceType prefix — the raw per-field + // faker picked gpu/instanceType independently, producing dupes + // and mismatched pairs. + "/deploymentSpecifications$/": () => { + const gpus = [ + { gpu: "A100", mem: "80GB", gpuMemory: "80Gi" }, + { gpu: "A10G", mem: "24GB", gpuMemory: "24Gi" }, + { gpu: "H100", mem: "80GB", gpuMemory: "80Gi" }, + { gpu: "L40S", mem: "48GB", gpuMemory: "48Gi" }, + ]; + const combos = gpus.flatMap((g) => + ["1x", "2x", "4x", "8x"].map((count) => ({ ...g, count })), + ); + return faker.helpers + .arrayElements(combos, faker.number.int({ min: 1, max: 5 })) + .map((c) => ({ + gpu: c.gpu, + instanceType: `${c.gpu}.${c.mem}.${c.count}`, + gpuMemory: c.gpuMemory, + systemMemory: faker.helpers.arrayElement([ + "256Gi", + "512Gi", + "1024Gi", + ]), + storage: faker.helpers.arrayElement([ + "512Gi", + "1Ti", + "2Ti", + ]), + minInstances: faker.number.int({ min: 0, max: 2 }), + maxInstances: faker.number.int({ min: 3, max: 16 }), + maxRequestConcurrency: faker.helpers.arrayElement([ + 1, 4, 8, 16, + ]), + regions: faker.helpers.arrayElements( + [ + "us-east-1", + "us-west-2", + "eu-central-1", + "ap-southeast-1", + ], + { min: 1, max: 2 }, + ), + clusters: faker.helpers.arrayElements( + ["prod-cluster-a", "prod-cluster-b", "prod-cluster-c"], + { min: 1, max: 2 }, + ), + })); + }, + "/backend/": () => + faker.helpers.arrayElement(["AWS", "GCP", "AZURE"]), + "/location/": () => + faker.helpers.arrayElement([ + "us-east-1", + "us-west-2", + "eu-central-1", + "ap-southeast-1", + ]), + }, + }, + }, + }, + }, + }, + }, + sis: { + input: { + target: "../spec/sis-openapi.yaml", + }, + output: { + ...sharedOutput, + override: { + ...sharedOutput.override, + tags: { + Clusters: { + mock: { + properties: { + "/clusterId/": () => faker.string.uuid(), + "/clusterGroupId/": () => faker.string.uuid(), + "/clusterKeyId/": () => faker.string.uuid(), + "/clusterName/": () => { + const user = faker.internet.username().toLowerCase(); + return `${user}-cluster`; + }, + "/clusterGroupName/": () => { + const user = faker.internet.username().toLowerCase(); + return `${user}-cluster`; + }, + "/region/": () => + faker.helpers.arrayElement([ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-central-1", + "ap-south-1", + "ap-northeast-1", + ]), + "/k8sVersion/": () => + `v1.${faker.number.int({ min: 29, max: 34 })}.${faker.number.int({ min: 0, max: 9 })}`, + "/nvcaVersion/": () => + `3.0.${faker.number.int({ min: 0, max: 5 })}`, + "/driverVersion/": () => + faker.helpers.arrayElement([ + "555.42.06", + "560.28.03", + "565.57.01", + ]), + // Build the gpus array so each GPU is unique and its instance + // types are unique + coherent (name = gpu.mem.count). The raw + // per-field faker duped GPU names and instance-type names. + "/gpus$/": () => { + const gpuDefs = [ + { name: "A100", mem: "80GB", gpuMemory: "80Gi" }, + { name: "A10G", mem: "24GB", gpuMemory: "24Gi" }, + { name: "H100", mem: "80GB", gpuMemory: "80Gi" }, + { name: "L40S", mem: "48GB", gpuMemory: "48Gi" }, + { name: "H200", mem: "141GB", gpuMemory: "141Gi" }, + ]; + return faker.helpers + .arrayElements( + gpuDefs, + faker.number.int({ min: 1, max: 3 }), + ) + .map((g) => ({ + name: g.name, + capacity: faker.helpers.arrayElement([8, 16, 32, 64]), + instanceTypes: faker.helpers + .arrayElements( + ["1x", "2x", "4x", "8x"], + faker.number.int({ min: 1, max: 3 }), + ) + .map((count) => ({ + name: `${g.name}.${g.mem}.${count}`, + value: `${g.name}.${g.mem}.${count}`, + gpuCount: Number(count.replace("x", "")), + gpuMemory: g.gpuMemory, + systemMemory: faker.helpers.arrayElement([ + "256Gi", + "512Gi", + "1024Gi", + ]), + cpuCores: faker.helpers.arrayElement([ + 16, 32, 64, 128, + ]), + default: false, + })), + })); + }, + "/gpuUsage/": () => { + const gpuName = faker.helpers.arrayElement([ + "H100", + "A100", + "L40S", + "H200", + ]); + const capacity = faker.helpers.arrayElement([8, 16, 32, 64]); + const allocated = faker.number.int({ + min: 0, + max: capacity, + }); + return { + [gpuName]: { + capacity, + allocated, + available: capacity - allocated, + }, + }; + }, + "/gpuCount/": () => + faker.helpers.arrayElement([1, 2, 4, 8, 16]), + "/gpuMemory/": () => + faker.helpers.arrayElement([ + "79Gi", + "159Gi", + "318Gi", + "637Gi", + "1274Gi", + ]), + "/systemMemory/": () => + faker.helpers.arrayElement([ + "8034Mi", + "15Gi", + "31Gi", + "62Gi", + "125Gi", + ]), + "/storage/": () => + faker.helpers.arrayElement([ + "104Gi", + "250Gi", + "455Gi", + "911Gi", + "1823Gi", + ]), + }, + }, + }, + }, + }, + }, + }, +}); diff --git a/src/uis/nvcf-ui/ui/package.json b/src/uis/nvcf-ui/ui/package.json new file mode 100644 index 000000000..2953ba118 --- /dev/null +++ b/src/uis/nvcf-ui/ui/package.json @@ -0,0 +1,73 @@ +{ + "name": "ui", + "private": true, + "type": "module", + "imports": { + "#/*": "./src/*" + }, + "scripts": { + "prepare": "kui-sync-skills --dir ../.claude", + "generate": "orval", + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "test": "vitest run", + "format": "biome format", + "lint": "biome lint", + "check": "biome check", + "check:fix": "biome check --write", + "vendor-css": "node scripts/vendor-kui-css.js" + }, + "dependencies": { + "@nvidia/foundations-react-core": "^1.7.0", + "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-query": "^5.100.1", + "@tanstack/react-router": "latest", + "@tanstack/react-table": "^8.21.3", + "clsx": "^2.1.1", + "dayjs": "^1.11.21", + "lodash-es": "^4.18.1", + "lucide-react": "^1.3.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-error-boundary": "^6.1.2", + "tailwindcss": "^4.1.18", + "zod": "^4.3.6" + }, + "devDependencies": { + "@biomejs/biome": "2.4.5", + "@faker-js/faker": "^10.4.0", + "@nvidia/foundations-tailwind-plugin": "^1.7.0", + "@tailwindcss/typography": "^0.5.16", + "@tanstack/devtools-vite": "latest", + "@tanstack/react-devtools": "latest", + "@tanstack/react-query-devtools": "^5.100.0", + "@tanstack/react-router-devtools": "latest", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/lodash-es": "^4.17.12", + "@types/node": "^22.10.2", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "4.1.5", + "happy-dom": "^20.9.0", + "msw": "^2.13.6", + "orval": "^8.9.0", + "rollup-plugin-visualizer": "^7.0.1", + "typescript": "^5.7.2", + "vite": "^8.0.0", + "vitest": "^4.1.5" + }, + "kui": { + "disablePostInstall": true + }, + "msw": { + "workerDirectory": [ + "public" + ] + }, + "packageManager": "pnpm@11.3.0+sha512.2c403d6594527287672b1f7056343a1f7c3634036a67ffabfcc2b3d7595d843768f8787148d1b57cf7956c90606bbd192857c363af19e96d2d0ec9ec5741d215" +} diff --git a/src/uis/nvcf-ui/ui/pnpm-lock.yaml b/src/uis/nvcf-ui/ui/pnpm-lock.yaml new file mode 100644 index 000000000..1d3dd0952 --- /dev/null +++ b/src/uis/nvcf-ui/ui/pnpm-lock.yaml @@ -0,0 +1,4994 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@nvidia/foundations-react-core': + specifier: ^1.7.0 + version: 1.7.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tailwindcss/vite': + specifier: ^4.1.18 + version: 4.2.4(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3)) + '@tanstack/react-query': + specifier: ^5.100.1 + version: 5.100.1(react@19.2.5) + '@tanstack/react-router': + specifier: latest + version: 1.170.18(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + dayjs: + specifier: ^1.11.21 + version: 1.11.21 + lodash-es: + specifier: ^4.18.1 + version: 4.18.1 + lucide-react: + specifier: ^1.3.0 + version: 1.3.0(react@19.2.5) + react: + specifier: ^19.2.0 + version: 19.2.5 + react-dom: + specifier: ^19.2.0 + version: 19.2.5(react@19.2.5) + react-error-boundary: + specifier: ^6.1.2 + version: 6.1.2(react@19.2.5) + tailwindcss: + specifier: ^4.1.18 + version: 4.2.4 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@biomejs/biome': + specifier: 2.4.5 + version: 2.4.5 + '@faker-js/faker': + specifier: ^10.4.0 + version: 10.4.0 + '@nvidia/foundations-tailwind-plugin': + specifier: ^1.7.0 + version: 1.7.0 + '@tailwindcss/typography': + specifier: ^0.5.16 + version: 0.5.19(tailwindcss@4.2.4) + '@tanstack/devtools-vite': + specifier: latest + version: 0.8.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3)) + '@tanstack/react-devtools': + specifier: latest + version: 0.10.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(solid-js@1.9.12) + '@tanstack/react-query-devtools': + specifier: ^5.100.0 + version: 5.100.0(@tanstack/react-query@5.100.1(react@19.2.5))(react@19.2.5) + '@tanstack/react-router-devtools': + specifier: latest + version: 1.167.0(@tanstack/react-router@1.170.18(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.171.15)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/node': + specifier: ^22.10.2 + version: 22.19.17 + '@types/react': + specifier: ^19.2.0 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.0 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3)) + '@vitest/coverage-v8': + specifier: 4.1.5 + version: 4.1.5(vitest@4.1.5) + happy-dom: + specifier: ^20.9.0 + version: 20.9.0 + msw: + specifier: ^2.13.6 + version: 2.13.6(@types/node@22.19.17)(typescript@5.9.3) + orval: + specifier: ^8.9.0 + version: 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + rollup-plugin-visualizer: + specifier: ^7.0.1 + version: 7.0.1(rolldown@1.0.0-rc.17) + typescript: + specifier: ^5.7.2 + version: 5.9.3 + vite: + specifier: ^8.0.0 + version: 8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3) + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(msw@2.13.6(@types/node@22.19.17)(typescript@5.9.3))(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3)) + +packages: + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@biomejs/biome@2.4.5': + resolution: {integrity: sha512-OWNCyMS0Q011R6YifXNOg6qsOg64IVc7XX6SqGsrGszPbkVCoaO7Sr/lISFnXZ9hjQhDewwZ40789QmrG0GYgQ==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.5': + resolution: {integrity: sha512-lGS4Nd5O3KQJ6TeWv10mElnx1phERhBxqGP/IKq0SvZl78kcWDFMaTtVK+w3v3lusRFxJY78n07PbKplirsU5g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.5': + resolution: {integrity: sha512-6MoH4tyISIBNkZ2Q5T1R7dLd5BsITb2yhhhrU9jHZxnNSNMWl+s2Mxu7NBF8Y3a7JJcqq9nsk8i637z4gqkJxQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.5': + resolution: {integrity: sha512-iqLDgpzobG7gpBF0fwEVS/LT8kmN7+S0E2YKFDtqliJfzNLnAiV2Nnyb+ehCDCJgAZBASkYHR2o60VQWikpqIg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.4.5': + resolution: {integrity: sha512-U1GAG6FTjhAO04MyH4xn23wRNBkT6H7NentHh+8UxD6ShXKBm5SY4RedKJzkUThANxb9rUKIPc7B8ew9Xo/cWg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.4.5': + resolution: {integrity: sha512-NlKa7GpbQmNhZf9kakQeddqZyT7itN7jjWdakELeXyTU3pg/83fTysRRDPJD0akTfKDl6vZYNT9Zqn4MYZVBOA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.4.5': + resolution: {integrity: sha512-NdODlSugMzTlENPTa4z0xB82dTUlCpsrOxc43///aNkTLblIYH4XpYflBbf5ySlQuP8AA4AZd1qXhV07IdrHdQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.4.5': + resolution: {integrity: sha512-EBfrTqRIWOFSd7CQb/0ttjHMR88zm3hGravnDwUA9wHAaCAYsULKDebWcN5RmrEo1KBtl/gDVJMrFjNR0pdGUw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.5': + resolution: {integrity: sha512-Pmhv9zT95YzECfjEHNl3mN9Vhusw9VA5KHY0ZvlGsxsjwS5cb7vpRnHzJIv0vG7jB0JI7xEaMH9ddfZm/RozBw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@commander-js/extra-typings@14.0.0': + resolution: {integrity: sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg==} + peerDependencies: + commander: ~14.0.0 + + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@faker-js/faker@10.4.0': + resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==} + engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} + + '@gerrit0/mini-shiki@3.23.0': + resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==} + + '@inquirer/ansi@2.0.5': + resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/confirm@6.0.12': + resolution: {integrity: sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.1.9': + resolution: {integrity: sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.5': + resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/type@4.0.5': + resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mswjs/interceptors@0.41.6': + resolution: {integrity: sha512-qmDvJIjcNsZ6tXWy2G9yuCgMPTTn35GMA3dPpSLm7QJVpbQzYdw0ALy1bKoivXnEM3U93/OrK+/M719b+fg84Q==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nvidia/foundations-react-core@1.7.0': + resolution: {integrity: sha512-F72H2veujWqh9AzBLlnsHKl2zfiBtm7xE/befrLESRHuysDddQrSiyCzIZovcX0eTvae+8GrgiCp0FE4xgTEdQ==} + hasBin: true + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@nvidia/foundations-tailwind-plugin@1.7.0': + resolution: {integrity: sha512-fqkyueTU4c6ecz5jbz9VWo5VyogIdMjzaWzsjMhhmBMoZwGhz7XFyhI+d30Z1Ou2LdoUaJYv9A+MvATyJBZH0A==} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@orval/angular@8.9.0': + resolution: {integrity: sha512-jYepvb8v4xHeJZskaqi8i+V7zp+rha+sK/A2Inyf10Gt7OoaX0G3P/wa3G6/K6nw4TfiLj5KKdIdG+c4X/luOw==} + + '@orval/axios@8.9.0': + resolution: {integrity: sha512-Qumi+SsjwphVtm65I5XW7tf7itvytcz8eW6aZElaybZRBrIIC9i62jTGYIDmY/loI8jSLublxEU6CmaSUyah2A==} + + '@orval/core@8.9.0': + resolution: {integrity: sha512-kcwr5ePPSaSqmFKWRI2skPd6MgPSdWk7NIfVaj61CdK5Q5agA173iUypdOwLif1xPF03nVvCdic3FZc77/XsMg==} + peerDependencies: + '@faker-js/faker': '>=10' + peerDependenciesMeta: + '@faker-js/faker': + optional: true + + '@orval/fetch@8.9.0': + resolution: {integrity: sha512-nFWeKh0iExCgo/yyx8XLr+PBgVteAoePF+GiQdiiqyEuM+Jd+aFg6nzHYoD9C/WYhnxoLHDfM/57EjHBRgpNtQ==} + + '@orval/hono@8.9.0': + resolution: {integrity: sha512-HU6xbeG4CYA/gFpBoqT2x6lM81gckzjhQDE2/YP5BESjTOGcHsz/Ngg1kDh0dNyxT5Z0BrW3preqb+7imtb8Lw==} + + '@orval/mcp@8.9.0': + resolution: {integrity: sha512-/jNEqnU6tny+9Y2b+rA0RwMqgAZOBEUXN/S18gnelJOe66jydCrEN89GXz9Tropmu0y4LleUwRCx1uo0pBhQ0w==} + + '@orval/mock@8.9.0': + resolution: {integrity: sha512-wJUG3qgORn4LfUJqoZLvMBCYAwXF2zIf+uADv42TF2wxQ80cw1uzlIb+jLTqdBiV6G4jJLGxFarP9RiWLogf8w==} + + '@orval/query@8.9.0': + resolution: {integrity: sha512-lwuqA+pwDX8EAc505tOKpDU7ayN+TzzLCgk4J2EXbRPvQvqgh0G8lJVdMYLKFxcDa9wzuxoV05SV50mkd7CsgA==} + + '@orval/solid-start@8.9.0': + resolution: {integrity: sha512-9BtRFi5ZzB65mYc+/Ip/4NVZ+vonC+v7ikH7wPo1W0SOsvVo8eE7GwoeoGK44yZXdhDn3fKeQZvYOpa38qNI7w==} + + '@orval/swr@8.9.0': + resolution: {integrity: sha512-z+OwrcibBDw8TEKwQd9DtlX4Ub469fxm1BrouSkkxry3b5zntGEAobOQYnCLkYDn/KUjJEJSc7fJHRWX1lV1sg==} + + '@orval/zod@8.9.0': + resolution: {integrity: sha512-KZ/JSDHIlt//ErwVqzw5DxjxUYoOq4wj1MFfuHMUBTJL9cPlXbyChgWE/rEMYOJI4i1zLjNSJfcQASuQad3zDw==} + + '@oxc-parser/binding-android-arm-eabi@0.120.0': + resolution: {integrity: sha512-WU3qtINx802wOl8RxAF1v0VvmC2O4D9M8Sv486nLeQ7iPHVmncYZrtBhB4SYyX+XZxj2PNnCcN+PW21jHgiOxg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.120.0': + resolution: {integrity: sha512-SEf80EHdhlbjZEgzeWm0ZA/br4GKMenDW3QB/gtyeTV1gStvvZeFi40ioHDZvds2m4Z9J1bUAUL8yn1/+A6iGg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.120.0': + resolution: {integrity: sha512-xVrrbCai8R8CUIBu3CjryutQnEYhZqs1maIqDvtUCFZb8vY33H7uh9mHpL3a0JBIKoBUKjPH8+rzyAeXnS2d6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.120.0': + resolution: {integrity: sha512-xyHBbnJ6mydnQUH7MAcafOkkrNzQC6T+LXgDH/3InEq2BWl/g424IMRiJVSpVqGjB+p2bd0h0WRR8iIwzjU7rw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.120.0': + resolution: {integrity: sha512-UMnVRllquXUYTeNfFKmxTTEdZ/ix1nLl0ducDzMSREoWYGVIHnOOxoKMWlCOvRr9Wk/HZqo2rh1jeumbPGPV9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.120.0': + resolution: {integrity: sha512-tkvn2CQ7QdcsMnpfiX3fd3wA3EFsWKYlcQzq9cFw/xc89Al7W6Y4O0FgLVkVQpo0Tnq/qtE1XfkJOnRRA9S/NA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.120.0': + resolution: {integrity: sha512-WN5y135Ic42gQDk9grbwY9++fDhqf8knN6fnP+0WALlAUh4odY/BDK1nfTJRSfpJD9P3r1BwU0m3pW2DU89whQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.120.0': + resolution: {integrity: sha512-1GgQBCcXvFMw99EPdMy+4NZ3aYyXsxjf9kbUUg8HuAy3ZBXzOry5KfFEzT9nqmgZI1cuetvApkiJBZLAPo8uaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.120.0': + resolution: {integrity: sha512-gmMQ70gsPdDBgpcErvJEoWNBr7bJooSLlvOBVBSGfOzlP5NvJ3bFvnUeZZ9d+dPrqSngtonf7nyzWUTUj/U+lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.120.0': + resolution: {integrity: sha512-T/kZuU0ajop0xhzVMwH5r3srC9Nqup5HaIo+3uFjIN5uPxa0LvSxC1ZqP4aQGJVW5G0z8/nCkjIfSMS91P/wzw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.120.0': + resolution: {integrity: sha512-vn21KXLAXzaI3N5CZWlBr1iWeXLl9QFIMor7S1hUjUGTeUuWCoE6JZB040/ZNDwf+JXPX8Ao9KbmJq9FMC2iGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.120.0': + resolution: {integrity: sha512-SUbUxlar007LTGmSLGIC5x/WJvwhdX+PwNzFJ9f/nOzZOrCFbOT4ikt7pJIRg1tXVsEfzk5mWpGO1NFiSs4PIw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.120.0': + resolution: {integrity: sha512-hYiPJTxyfJY2+lMBFk3p2bo0R9GN+TtpPFlRqVchL1qvLG+pznstramHNvJlw9AjaoRUHwp9IKR7UZQnRPGjgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.120.0': + resolution: {integrity: sha512-q+5jSVZkprJCIy3dzJpApat0InJaoxQLsJuD6DkX8hrUS61z2lHQ1Fe9L2+TYbKHXCLWbL0zXe7ovkIdopBGMQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.120.0': + resolution: {integrity: sha512-D9QDDZNnH24e7X4ftSa6ar/2hCavETfW3uk0zgcMIrZNy459O5deTbWrjGzZiVrSWigGtlQwzs2McBP0QsfV1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.120.0': + resolution: {integrity: sha512-TBU8ZwOUWAOUWVfmI16CYWbvh4uQb9zHnGBHsw5Cp2JUVG044OIY1CSHODLifqzQIMTXvDvLzcL89GGdUIqNrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.120.0': + resolution: {integrity: sha512-WG/FOZgDJCpJnuF3ToG/K28rcOmSY7FmFmfBKYb2fmLyhDzPpUldFGV7/Fz4ru0Iz/v4KPmf8xVgO8N3lO4KHA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.120.0': + resolution: {integrity: sha512-1T0HKGcsz/BKo77t7+89L8Qvu4f9DoleKWHp3C5sJEcbCjDOLx3m9m722bWZTY+hANlUEs+yjlK+lBFsA+vrVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.120.0': + resolution: {integrity: sha512-L7vfLzbOXsjBXV0rv/6Y3Jd9BRjPeCivINZAqrSyAOZN3moCopDN+Psq9ZrGNZtJzP8946MtlRFZ0Als0wBCOw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.120.0': + resolution: {integrity: sha512-ys+upfqNtSu58huAhJMBKl3XCkGzyVFBlMlGPzHeFKgpFF/OdgNs1MMf8oaJIbgMH8ZxgGF7qfue39eJohmKIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.120.0': + resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==} + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.8': + resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + + '@scalar/helpers@0.5.2': + resolution: {integrity: sha512-Pi1GAl8jO6ungmGj2sjDfCfqiBNrKW6HXDZmminV94ybGU/KtRLOqHwd0n9FIhY3j0RYGpGC0VCuniCICfQPHg==} + engines: {node: '>=22'} + + '@scalar/json-magic@0.12.8': + resolution: {integrity: sha512-a559iO8tmFeA90JJAAM3U5x1Asf3mr0Z8uDC1PmyLTDjdSOfajP7EY9VzNoXE2cM48ilf9qrjmkbw/d4VCFjQw==} + engines: {node: '>=22'} + + '@scalar/openapi-parser@0.25.12': + resolution: {integrity: sha512-1hajBAbc7cbEcsSZEQxaPXZyCjMf6h6hObV+SO32jkC6rrxinPXQIucDu9HTu/jm/FaaMnNhc8/XDWz5/E49cQ==} + engines: {node: '>=22'} + + '@scalar/openapi-types@0.8.0': + resolution: {integrity: sha512-WmaxVSfvY5K/TwcG2B2TU1WOe1As1uc2s7myswtP6dBlcjU3hM08SApxv/jmyGaCE8t4gO5BBhmHY4pDUfmr2g==} + engines: {node: '>=22'} + + '@scalar/openapi-upgrader@0.2.6': + resolution: {integrity: sha512-pvEmfSCDNYR4+lygidUqfo+shzyp4OSh9+UgK110rzA8Oot6WbJBM03Fuq3M255G7G6R9iXyfsebB7MBUocPkw==} + engines: {node: '>=22'} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@shikijs/core@4.1.0': + resolution: {integrity: sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.1.0': + resolution: {integrity: sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/langs@4.1.0': + resolution: {integrity: sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.1.0': + resolution: {integrity: sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw==} + engines: {node: '>=20'} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/themes@4.1.0': + resolution: {integrity: sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw==} + engines: {node: '>=20'} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/types@4.1.0': + resolution: {integrity: sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@solid-primitives/event-listener@2.4.5': + resolution: {integrity: sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/keyboard@1.3.5': + resolution: {integrity: sha512-sav+l+PL+74z3yaftVs7qd8c2SXkqzuxPOVibUe5wYMt+U5Hxp3V3XCPgBPN2I6cANjvoFtz0NiU8uHVLdi9FQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/resize-observer@2.1.5': + resolution: {integrity: sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/rootless@1.5.3': + resolution: {integrity: sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/static-store@0.1.3': + resolution: {integrity: sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/utils@6.4.0': + resolution: {integrity: sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A==} + peerDependencies: + solid-js: ^1.6.12 + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tabby_ai/hijri-converter@1.0.5': + resolution: {integrity: sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==} + engines: {node: '>=16.0.0'} + + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + engines: {node: '>= 20'} + + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tanstack/devtools-client@0.0.8': + resolution: {integrity: sha512-cG3iZkGWCwN330bLBKa8+9r4Of2AXNoz2zUqcsy/4XsD3105ghVBx78cGyvJj9fSclNomPxoqAnDGXXhg1WLvA==} + engines: {node: '>=18'} + + '@tanstack/devtools-event-bus@0.4.2': + resolution: {integrity: sha512-2LHzhwBFlKHCcklsQrGe8TeyjHd4XAF8nuCO6wHmva5fePUkJUULbu6CsCNAlGlCi0KkEsMXZSvRdR4HgMq4yA==} + engines: {node: '>=18'} + + '@tanstack/devtools-event-client@0.5.0': + resolution: {integrity: sha512-H+OH3zC6Vhu/K0NaVfQKknEKawc/+2PT+D3SB3Ox0V8SiMlTo0abbmH2rH0721R2aNYbjdMXA1oENOd8E2UVoA==} + engines: {node: '>=18'} + hasBin: true + + '@tanstack/devtools-ui@0.6.0': + resolution: {integrity: sha512-CVaM6rT6Nl5ijo83vJYFa2SjofvpuOl/uOvbYGhBrRgUhhelNHhx8zZX+hnZCHmIr0/lzM65hsocnZ72592Rvg==} + engines: {node: '>=18'} + peerDependencies: + solid-js: '>=1.9.7' + + '@tanstack/devtools-vite@0.8.1': + resolution: {integrity: sha512-oQxOo0fI0bwhHtw/psFlIR0OS/bsKrirBxwnw2vuhCM4bjt3k4EZZsW/lvZ1+Vpouhts7LSyvngnxvGXbQ1sUQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@tanstack/devtools@0.12.5': + resolution: {integrity: sha512-JdxTSeVdjJheycgz4c7qbldNKDCEDWlWr1l9dZBhd9sOmRBT5Z70ka9Eb8mb+FUnalcOIB62IDSR/iSxAIUD8Q==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + solid-js: '>=1.9.7' + + '@tanstack/history@1.162.0': + resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==} + engines: {node: '>=20.19'} + + '@tanstack/query-core@5.100.1': + resolution: {integrity: sha512-awvQhOO/2TrSCHE5LKKsXcvvj6WSBncwEcMFCB/ez0Qs0b17iyyivoGArNV3HFfXryZwCpnb/olsaBBKrIbtSw==} + + '@tanstack/query-devtools@5.100.0': + resolution: {integrity: sha512-6l1vHtvZSpqKVbbRokaX3HJ+MDRFaPHQ1odOVkIibWJafrLi9pMtZDtyQmno2h0J6dmLzH9PxsKKPpGf1Uh/pA==} + + '@tanstack/react-devtools@0.10.8': + resolution: {integrity: sha512-YJV6YttQf9lhhPbPBLULgy1eScEvJUMsCS26mjg9hfBKgAJQA5sF9zvzorDzW9Ob6o/asoXikO81JnRUVuFX0Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=16.8' + '@types/react-dom': '>=16.8' + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/react-query-devtools@5.100.0': + resolution: {integrity: sha512-TdDAmg6cqqPdBSZQuH7OghJPV03v8wnlEvHT9utN38D2mmD+I/Jb0bM5v7I5avNAPDerSKwlB/6AI1dWHinvug==} + peerDependencies: + '@tanstack/react-query': ^5.100.0 + react: ^18 || ^19 + + '@tanstack/react-query@5.100.1': + resolution: {integrity: sha512-UgWRLhQKprC37SsO6y1zRabOqDmM2gsdTNPbqTT35yl7kOOhwXU4nyfOiGHXPwoEFJV1IpSk85hjIFjNFWVpzw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-router-devtools@1.167.0': + resolution: {integrity: sha512-nGw095EG7IHx0h5NtlEmzf6vcCTaFNPWdTSuDKazajhN0ct/v/TkekJ9J6KYUCeV1a8/2ZmToc58M+0rrOyn7w==} + engines: {node: '>=20.19'} + peerDependencies: + '@tanstack/react-router': ^1.170.0 + '@tanstack/router-core': ^1.170.0 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + '@tanstack/router-core': + optional: true + + '@tanstack/react-router@1.170.18': + resolution: {integrity: sha512-wpbGYZEp/fmz1q4bn7BD8VZ+/VZ7GBqSJv5V969pU+chP8y7dquWDmKTFMohvUegb9lg12m1uPVvD6kB2wORvQ==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/router-core@1.171.15': + resolution: {integrity: sha512-IILCDcLaItMZQ2jEmCABHY1Nhjjn5XUvwpQp3e4Nmu+vfg0BgYFuu/QASz2SwE2ZNbVMrvt8X/wxa+Gg5aErxA==} + engines: {node: '>=20.19'} + + '@tanstack/router-devtools-core@1.168.0': + resolution: {integrity: sha512-wQoQhlBK7nlZgqzaqdYXKWNTpdHdsaREdaPhFZVH0/Ador+F+eM3/NF2i3f2LPeS0GgKraZUQXe1Q/1+KHyEYg==} + engines: {node: '>=20.19'} + peerDependencies: + '@tanstack/router-core': ^1.170.0 + csstype: ^3.0.10 + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher + + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} + peerDependencies: + '@vitest/browser': 4.1.5 + vitest: 4.1.5 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@8.0.0: + resolution: {integrity: sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==} + engines: {node: '>=20'} + + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + globby@16.1.0: + resolution: {integrity: sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==} + engines: {node: '>=20'} + + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} + peerDependencies: + csstype: ^3.0.10 + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphql@16.13.2: + resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + happy-dom@20.9.0: + resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} + engines: {node: '>=20.0.0'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@4.0.0: + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isbot@5.1.39: + resolution: {integrity: sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + + launch-editor@2.13.2: + resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==} + + leven@4.1.0: + resolution: {integrity: sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + locate-path@8.0.0: + resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==} + engines: {node: '>=20'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lucide-react@1.3.0: + resolution: {integrity: sha512-aKUU4jKpXe26Y3xmF3DGg+NXHteRCVT96r/+Jp7wziqvmFr0/x8ReRsES0wHgTvW7OLxiHvFLN+YeyDnK6h0dQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} + hasBin: true + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.13.6: + resolution: {integrity: sha512-GAJbQy8Ra/Ydjt0Hb2MGT2qhzd83J3+QZMHdH85uW7r/XkKc846+Ma2PLif5hGvTm5Yqa+wkcstpim0WeLZU9g==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-html-parser@7.1.0: + resolution: {integrity: sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + + orval@8.9.0: + resolution: {integrity: sha512-KjXCXWHFbKIRouWeP5ZOy/4YO60KHaElyvCcrLO3+JEYNvcpy79IQbF8qV1akdfxFP0+9RXJg1fDVibo95681A==} + engines: {node: '>=22.18.0'} + hasBin: true + peerDependencies: + prettier: '>=3.0.0' + peerDependenciesMeta: + prettier: + optional: true + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + oxc-parser@0.120.0: + resolution: {integrity: sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w==} + engines: {node: ^20.19.0 || >=22.12.0} + + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-compiler-runtime@1.0.0: + resolution: {integrity: sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental + + react-day-picker@9.14.0: + resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + react-error-boundary@6.1.2: + resolution: {integrity: sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + remeda@2.34.0: + resolution: {integrity: sha512-zL4cEPkLHxwmlDRPyvJZjojpG5M5HXrDiABNKof+dq7kkuyQttP6NrF2uJB0DKIU09K8cTq+sQDlbo2r7mdR5Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rettime@0.11.8: + resolution: {integrity: sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup-plugin-visualizer@7.0.1: + resolution: {integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==} + engines: {node: '>=22'} + hasBin: true + peerDependencies: + rolldown: 1.x || ^1.0.0-beta || ^1.0.0-rc + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + seroval-plugins@1.5.4: + resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.4: + resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} + engines: {node: '>=10'} + + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + solid-js@1.9.12: + resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.28: + resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==} + + tldts@7.0.28: + resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@5.6.0: + resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} + engines: {node: '>=20'} + + typedoc-plugin-coverage@4.0.3: + resolution: {integrity: sha512-baim3wyMkqpX7rBzL/6iZ7wzKJuSr9ffP16RHOsdTUNoHUZeXLIZHSUBtUhXmNHaUNRgfqdmKLBwyggbJjGdeQ==} + engines: {node: '>= 18'} + peerDependencies: + typedoc: 0.28.x + + typedoc-plugin-markdown@4.11.0: + resolution: {integrity: sha512-2iunh2ALyfyh204OF7h2u0kuQ84xB3jFZtFyUr01nThJkLvR8oGGSSDlyt2gyO4kXhvUxDcVbO0y43+qX+wFbw==} + engines: {node: '>= 18'} + peerDependencies: + typedoc: 0.28.x + + typedoc@0.28.19: + resolution: {integrity: sha512-wKh+lhdmMFivMlc6vRRcMGXeGEHGU2g8a2CkPTJjJlwRf1iXbimWIPcFolCqe4E0d/FRtGszpIrsp3WLpDB8Pw==} + engines: {node: '>= 18', pnpm: '>= 10'} + hasBin: true + peerDependencies: + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unicorn-magic@0.4.0: + resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} + engines: {node: '>=20'} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@adobe/css-tools@4.4.4': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.29.2': {} + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@biomejs/biome@2.4.5': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.5 + '@biomejs/cli-darwin-x64': 2.4.5 + '@biomejs/cli-linux-arm64': 2.4.5 + '@biomejs/cli-linux-arm64-musl': 2.4.5 + '@biomejs/cli-linux-x64': 2.4.5 + '@biomejs/cli-linux-x64-musl': 2.4.5 + '@biomejs/cli-win32-arm64': 2.4.5 + '@biomejs/cli-win32-x64': 2.4.5 + + '@biomejs/cli-darwin-arm64@2.4.5': + optional: true + + '@biomejs/cli-darwin-x64@2.4.5': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.5': + optional: true + + '@biomejs/cli-linux-arm64@2.4.5': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.5': + optional: true + + '@biomejs/cli-linux-x64@2.4.5': + optional: true + + '@biomejs/cli-win32-arm64@2.4.5': + optional: true + + '@biomejs/cli-win32-x64@2.4.5': + optional: true + + '@commander-js/extra-typings@14.0.0(commander@14.0.3)': + dependencies: + commander: 14.0.3 + + '@date-fns/tz@1.4.1': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@faker-js/faker@10.4.0': {} + + '@gerrit0/mini-shiki@3.23.0': + dependencies: + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@inquirer/ansi@2.0.5': {} + + '@inquirer/confirm@6.0.12(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.1.9(@types/node@22.19.17) + '@inquirer/type': 4.0.5(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 + + '@inquirer/core@11.1.9(@types/node@22.19.17)': + dependencies: + '@inquirer/ansi': 2.0.5 + '@inquirer/figures': 2.0.5 + '@inquirer/type': 4.0.5(@types/node@22.19.17) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 22.19.17 + + '@inquirer/figures@2.0.5': {} + + '@inquirer/type@4.0.5(@types/node@22.19.17)': + optionalDependencies: + '@types/node': 22.19.17 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mswjs/interceptors@0.41.6': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nvidia/foundations-react-core@1.7.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@date-fns/tz': 1.4.1 + '@radix-ui/react-label': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-progress': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@shikijs/core': 4.1.0 + '@shikijs/engine-javascript': 4.1.0 + '@shikijs/langs': 4.1.0 + '@shikijs/themes': 4.1.0 + '@shikijs/types': 4.1.0 + class-variance-authority: 0.7.1 + date-fns: 4.1.0 + node-html-parser: 7.1.0 + react: 19.2.5 + react-compiler-runtime: 1.0.0(react@19.2.5) + react-day-picker: 9.14.0(react@19.2.5) + react-dom: 19.2.5(react@19.2.5) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + '@nvidia/foundations-tailwind-plugin@1.7.0': {} + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/deferred-promise@3.0.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@orval/angular@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/axios@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/core@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@scalar/openapi-types': 0.8.0 + acorn: 8.16.0 + compare-versions: 6.1.1 + debug: 4.4.3 + esbuild: 0.27.7 + esutils: 2.0.3 + fs-extra: 11.3.4 + globby: 16.1.0 + jiti: 2.6.1 + remeda: 2.34.0 + typedoc: 0.28.19(typescript@5.9.3) + optionalDependencies: + '@faker-js/faker': 10.4.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@orval/fetch@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@scalar/openapi-types': 0.8.0 + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/hono@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/zod': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + fs-extra: 11.3.4 + remeda: 2.34.0 + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/mcp@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/fetch': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/zod': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/mock@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + remeda: 2.34.0 + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/query@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/fetch': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + remeda: 2.34.0 + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/solid-start@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@scalar/openapi-types': 0.8.0 + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/swr@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/fetch': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@orval/zod@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3)': + dependencies: + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + remeda: 2.34.0 + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + '@oxc-parser/binding-android-arm-eabi@0.120.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.120.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.120.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.120.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.120.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.120.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.120.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.120.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.120.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.120.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.120.0': + optional: true + + '@oxc-project/types@0.120.0': {} + + '@oxc-project/types@0.127.0': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-context@1.1.3(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@rolldown/binding-android-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.17': {} + + '@rolldown/pluginutils@1.0.0-rc.7': {} + + '@scalar/helpers@0.5.2': {} + + '@scalar/json-magic@0.12.8': + dependencies: + '@scalar/helpers': 0.5.2 + pathe: 2.0.3 + yaml: 2.8.3 + + '@scalar/openapi-parser@0.25.12': + dependencies: + '@scalar/helpers': 0.5.2 + '@scalar/json-magic': 0.12.8 + '@scalar/openapi-types': 0.8.0 + '@scalar/openapi-upgrader': 0.2.6 + ajv: 8.20.0 + ajv-draft-04: 1.0.0(ajv@8.20.0) + ajv-formats: 3.0.1(ajv@8.20.0) + jsonpointer: 5.0.1 + leven: 4.1.0 + yaml: 2.8.3 + + '@scalar/openapi-types@0.8.0': {} + + '@scalar/openapi-upgrader@0.2.6': + dependencies: + '@scalar/openapi-types': 0.8.0 + + '@sec-ant/readable-stream@0.4.1': {} + + '@shikijs/core@4.1.0': + dependencies: + '@shikijs/primitive': 4.1.0 + '@shikijs/types': 4.1.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.1.0': + dependencies: + '@shikijs/types': 4.1.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/langs@4.1.0': + dependencies: + '@shikijs/types': 4.1.0 + + '@shikijs/primitive@4.1.0': + dependencies: + '@shikijs/types': 4.1.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@4.1.0': + dependencies: + '@shikijs/types': 4.1.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/types@4.1.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@solid-primitives/event-listener@2.4.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/keyboard@1.3.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/resize-observer@2.1.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.12) + '@solid-primitives/static-store': 0.1.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/rootless@1.5.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/static-store@0.1.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/utils@6.4.0(solid-js@1.9.12)': + dependencies: + solid-js: 1.9.12 + + '@standard-schema/spec@1.1.0': {} + + '@tabby_ai/hijri-converter@1.0.5': {} + + '@tailwindcss/node@4.2.4': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.4 + + '@tailwindcss/oxide-android-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide@4.2.4': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/typography@0.5.19(tailwindcss@4.2.4)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 4.2.4 + + '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3) + + '@tanstack/devtools-client@0.0.8': + dependencies: + '@tanstack/devtools-event-client': 0.5.0 + + '@tanstack/devtools-event-bus@0.4.2': + dependencies: + ws: 8.20.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@tanstack/devtools-event-client@0.5.0': {} + + '@tanstack/devtools-ui@0.6.0(csstype@3.2.3)(solid-js@1.9.12)': + dependencies: + clsx: 2.1.1 + dayjs: 1.11.21 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.12 + transitivePeerDependencies: + - csstype + + '@tanstack/devtools-vite@0.8.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@tanstack/devtools-client': 0.0.8 + '@tanstack/devtools-event-bus': 0.4.2 + chalk: 5.6.2 + launch-editor: 2.13.2 + magic-string: 0.30.21 + oxc-parser: 0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + picomatch: 4.0.4 + vite: 8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - bufferutil + - utf-8-validate + + '@tanstack/devtools@0.12.5(csstype@3.2.3)(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/keyboard': 1.3.5(solid-js@1.9.12) + '@solid-primitives/resize-observer': 2.1.5(solid-js@1.9.12) + '@tanstack/devtools-client': 0.0.8 + '@tanstack/devtools-event-bus': 0.4.2 + '@tanstack/devtools-ui': 0.6.0(csstype@3.2.3)(solid-js@1.9.12) + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.12 + transitivePeerDependencies: + - bufferutil + - csstype + - utf-8-validate + + '@tanstack/history@1.162.0': {} + + '@tanstack/query-core@5.100.1': {} + + '@tanstack/query-devtools@5.100.0': {} + + '@tanstack/react-devtools@0.10.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(solid-js@1.9.12)': + dependencies: + '@tanstack/devtools': 0.12.5(csstype@3.2.3)(solid-js@1.9.12) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + transitivePeerDependencies: + - bufferutil + - csstype + - solid-js + - utf-8-validate + + '@tanstack/react-query-devtools@5.100.0(@tanstack/react-query@5.100.1(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/query-devtools': 5.100.0 + '@tanstack/react-query': 5.100.1(react@19.2.5) + react: 19.2.5 + + '@tanstack/react-query@5.100.1(react@19.2.5)': + dependencies: + '@tanstack/query-core': 5.100.1 + react: 19.2.5 + + '@tanstack/react-router-devtools@1.167.0(@tanstack/react-router@1.170.18(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.171.15)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/react-router': 1.170.18(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/router-devtools-core': 1.168.0(@tanstack/router-core@1.171.15)(csstype@3.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@tanstack/router-core': 1.171.15 + transitivePeerDependencies: + - csstype + + '@tanstack/react-router@1.170.18(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/history': 1.162.0 + '@tanstack/react-store': 0.9.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/router-core': 1.171.15 + isbot: 5.1.39 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@tanstack/react-store@0.9.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/store': 0.9.3 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + use-sync-external-store: 1.6.0(react@19.2.5) + + '@tanstack/react-table@8.21.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@tanstack/router-core@1.171.15': + dependencies: + '@tanstack/history': 1.162.0 + cookie-es: 3.1.1 + seroval: 1.5.4 + seroval-plugins: 1.5.4(seroval@1.5.4) + + '@tanstack/router-devtools-core@1.168.0(@tanstack/router-core@1.171.15)(csstype@3.2.3)': + dependencies: + '@tanstack/router-core': 1.171.15 + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + optionalDependencies: + csstype: 3.2.3 + + '@tanstack/store@0.9.3': {} + + '@tanstack/table-core@8.21.3': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.24 + + '@types/lodash@4.17.24': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 22.19.17 + + '@types/statuses@2.0.6': {} + + '@types/unist@3.0.3': {} + + '@types/whatwg-mimetype@3.0.2': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.19.17 + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-react@6.0.1(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3) + + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.5 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(msw@2.13.6(@types/node@22.19.17)(typescript@5.9.3))(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3)) + + '@vitest/expect@4.1.5': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.5(msw@2.13.6(@types/node@22.19.17)(typescript@5.9.3))(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.13.6(@types/node@22.19.17)(typescript@5.9.3) + vite: 8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3) + + '@vitest/pretty-format@4.1.5': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.5': + dependencies: + '@vitest/utils': 4.1.5 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.5': {} + + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + acorn@8.16.0: {} + + ajv-draft-04@1.0.0(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + balanced-match@4.0.4: {} + + boolbase@1.0.0: {} + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + ccount@2.0.1: {} + + chai@6.2.2: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@14.0.3: {} + + compare-versions@6.1.1: {} + + convert-source-map@2.0.0: {} + + cookie-es@3.1.1: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.2.2: {} + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + date-fns-jalali@4.1.0-0: {} + + date-fns@4.1.0: {} + + dayjs@1.11.21: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + entities@4.5.0: {} + + entities@7.0.1: {} + + es-module-lexer@2.1.0: {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.0: {} + + fast-wrap-ansi@0.2.0: + dependencies: + fast-string-width: 3.0.2 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@8.0.0: + dependencies: + locate-path: 8.0.0 + unicorn-magic: 0.3.0 + + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + globby@16.1.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + is-path-inside: 4.0.0 + slash: 5.1.0 + unicorn-magic: 0.4.0 + + goober@2.1.18(csstype@3.2.3): + dependencies: + csstype: 3.2.3 + + graceful-fs@4.2.11: {} + + graphql@16.13.2: {} + + happy-dom@20.9.0: + dependencies: + '@types/node': 22.19.17 + '@types/whatwg-mimetype': 3.0.2 + '@types/ws': 8.18.1 + entities: 7.0.1 + whatwg-mimetype: 3.0.0 + ws: 8.20.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + has-flag@4.0.0: {} + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + he@1.2.0: {} + + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.1.0 + + html-escaper@2.0.2: {} + + html-void-elements@3.0.0: {} + + human-signals@8.0.1: {} + + ignore@7.0.5: {} + + indent-string@4.0.0: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-node-process@1.2.0: {} + + is-number@7.0.0: {} + + is-path-inside@4.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isbot@5.1.39: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jiti@2.6.1: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@1.0.0: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonpointer@5.0.1: {} + + launch-editor@2.13.2: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.3 + + leven@4.1.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + locate-path@8.0.0: + dependencies: + p-locate: 6.0.0 + + lodash-es@4.18.1: {} + + lucide-react@1.3.0(react@19.2.5): + dependencies: + react: 19.2.5 + + lunr@2.3.9: {} + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + markdown-it@14.1.1: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdurl@2.0.0: {} + + merge2@1.4.1: {} + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + min-indent@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + ms@2.1.3: {} + + msw@2.13.6(@types/node@22.19.17)(typescript@5.9.3): + dependencies: + '@inquirer/confirm': 6.0.12(@types/node@22.19.17) + '@mswjs/interceptors': 0.41.6 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.13.2 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.11.8 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.1 + type-fest: 5.6.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@3.0.0: {} + + nanoid@3.3.11: {} + + node-html-parser@7.1.0: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + obug@2.1.1: {} + + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + orval@8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3): + dependencies: + '@commander-js/extra-typings': 14.0.0(commander@14.0.3) + '@orval/angular': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/axios': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/core': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/fetch': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/hono': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/mcp': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/mock': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/query': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/solid-start': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/swr': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@orval/zod': 8.9.0(@faker-js/faker@10.4.0)(typescript@5.9.3) + '@scalar/json-magic': 0.12.8 + '@scalar/openapi-parser': 0.25.12 + '@scalar/openapi-types': 0.8.0 + chokidar: 5.0.0 + commander: 14.0.3 + enquirer: 2.4.1 + execa: 9.6.1 + find-up: 8.0.0 + fs-extra: 11.3.4 + jiti: 2.6.1 + js-yaml: 4.1.1 + remeda: 2.34.0 + string-argv: 0.3.2 + tsconfck: 3.1.6(typescript@5.9.3) + typedoc: 0.28.19(typescript@5.9.3) + typedoc-plugin-coverage: 4.0.3(typedoc@0.28.19(typescript@5.9.3)) + typedoc-plugin-markdown: 4.11.0(typedoc@0.28.19(typescript@5.9.3)) + transitivePeerDependencies: + - '@faker-js/faker' + - supports-color + - typescript + + outvariant@1.4.3: {} + + oxc-parser@0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + dependencies: + '@oxc-project/types': 0.120.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.120.0 + '@oxc-parser/binding-android-arm64': 0.120.0 + '@oxc-parser/binding-darwin-arm64': 0.120.0 + '@oxc-parser/binding-darwin-x64': 0.120.0 + '@oxc-parser/binding-freebsd-x64': 0.120.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.120.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.120.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.120.0 + '@oxc-parser/binding-linux-arm64-musl': 0.120.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.120.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.120.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.120.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.120.0 + '@oxc-parser/binding-linux-x64-gnu': 0.120.0 + '@oxc-parser/binding-linux-x64-musl': 0.120.0 + '@oxc-parser/binding-openharmony-arm64': 0.120.0 + '@oxc-parser/binding-wasm32-wasi': 0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxc-parser/binding-win32-arm64-msvc': 0.120.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.120.0 + '@oxc-parser/binding-win32-x64-msvc': 0.120.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.2 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + parse-ms@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + powershell-utils@0.1.0: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + property-information@7.1.0: {} + + punycode.js@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-compiler-runtime@1.0.0(react@19.2.5): + dependencies: + react: 19.2.5 + + react-day-picker@9.14.0(react@19.2.5): + dependencies: + '@date-fns/tz': 1.4.1 + '@tabby_ai/hijri-converter': 1.0.5 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 19.2.5 + + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-error-boundary@6.1.2(react@19.2.5): + dependencies: + react: 19.2.5 + + react-is@17.0.2: {} + + react@19.2.5: {} + + readdirp@5.0.0: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + remeda@2.34.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + rettime@0.11.8: {} + + reusify@1.1.0: {} + + rolldown@1.0.0-rc.17: + dependencies: + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + + rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.17): + dependencies: + open: 11.0.0 + picomatch: 4.0.4 + source-map: 0.7.6 + yargs: 18.0.0 + optionalDependencies: + rolldown: 1.0.0-rc.17 + + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.27.0: {} + + semver@7.7.4: {} + + seroval-plugins@1.5.4(seroval@1.5.4): + dependencies: + seroval: 1.5.4 + + seroval@1.5.4: {} + + set-cookie-parser@3.1.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slash@5.1.0: {} + + solid-js@1.9.12: + dependencies: + csstype: 3.2.3 + seroval: 1.5.4 + seroval-plugins: 1.5.4(seroval@1.5.4) + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@4.1.0: {} + + strict-event-emitter@0.5.1: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@4.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tagged-tag@1.0.0: {} + + tailwindcss@4.2.4: {} + + tapable@2.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.1.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tldts-core@7.0.28: {} + + tldts@7.0.28: + dependencies: + tldts-core: 7.0.28 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.28 + + trim-lines@3.0.1: {} + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tslib@2.8.1: + optional: true + + type-fest@5.6.0: + dependencies: + tagged-tag: 1.0.0 + + typedoc-plugin-coverage@4.0.3(typedoc@0.28.19(typescript@5.9.3)): + dependencies: + typedoc: 0.28.19(typescript@5.9.3) + + typedoc-plugin-markdown@4.11.0(typedoc@0.28.19(typescript@5.9.3)): + dependencies: + typedoc: 0.28.19(typescript@5.9.3) + + typedoc@0.28.19(typescript@5.9.3): + dependencies: + '@gerrit0/mini-shiki': 3.23.0 + lunr: 2.3.9 + markdown-it: 14.1.1 + minimatch: 10.2.5 + typescript: 5.9.3 + yaml: 2.8.3 + + typescript@5.9.3: {} + + uc.micro@2.1.0: {} + + undici-types@6.21.0: {} + + unicorn-magic@0.3.0: {} + + unicorn-magic@0.4.0: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universalify@2.0.1: {} + + until-async@3.0.2: {} + + use-sync-external-store@1.6.0(react@19.2.5): + dependencies: + react: 19.2.5 + + util-deprecate@1.0.2: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.10 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 22.19.17 + esbuild: 0.27.7 + fsevents: 2.3.3 + jiti: 2.6.1 + yaml: 2.8.3 + + vitest@4.1.5(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(msw@2.13.6(@types/node@22.19.17)(typescript@5.9.3))(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(msw@2.13.6(@types/node@22.19.17)(typescript@5.9.3))(vite@8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.10(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.17 + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + happy-dom: 20.9.0 + transitivePeerDependencies: + - msw + + whatwg-mimetype@3.0.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + ws@8.20.0: {} + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + y18n@5.0.8: {} + + yaml@2.8.3: {} + + yargs-parser@21.1.1: {} + + yargs-parser@22.0.0: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yocto-queue@1.2.2: {} + + yoctocolors@2.1.2: {} + + zod@4.3.6: {} + + zwitch@2.0.4: {} diff --git a/src/uis/nvcf-ui/ui/pnpm-workspace.yaml b/src/uis/nvcf-ui/ui/pnpm-workspace.yaml new file mode 100644 index 000000000..4982ea28e --- /dev/null +++ b/src/uis/nvcf-ui/ui/pnpm-workspace.yaml @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +allowBuilds: + esbuild: true + lightningcss: true + "@nvidia/foundations-react-core": true + core-js: true + msw: true + protobufjs: true diff --git a/src/uis/nvcf-ui/ui/public/favicon.ico b/src/uis/nvcf-ui/ui/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9e35bef5b8258264d8e67b0ff69b4b29feecdcba GIT binary patch literal 25214 zcmeHP33wdEm42<3J!8qAWg;Ik2zzW{8y^sDCd6Wf1u; z0@U^Z;?F>HEIG|k>E9ntTB4=lk7&XSTol=Qd69;=$A_?_bga#>#7Ip?sp(k-Q%qq^TNG_#7HJDlIXPlYRw2K&lrQ{co zsS@liPjYT4{c0E#A9j-zPolG#j6Y9M1G^cD$K!FB(EhLiwi`%Dy9mrigJDbkhj|LGzYjA$0CfnE>n zM2ic)TDw8*1hQOWB+Io(5}oK?G{YrDtzS7wulK-cBm>or(GD#1Sok`j6bq2pjn!Pa z)9VE5wY^TL&%@dj?}EN`CnTZ?X~!~(OCHVT&ZX7d53n33APSRRPz|{>RxB(G@WhpL z2Rotmq2;iX>EZ&L&BDIOIZ3VyX{>y4`it(aMUg9#zYhuG(UFX2s9&Y(4lF^~j<{8* zbWVFq%kuX1UG0AP<0Ja|F7b)k-%qS@?Jbc$yRYW}`<+*7y5F$d-g_|i`WsI)H+Emv zQ*YP5G0v_(=?eSGY4!FSuQb{3^z_=Bapm=|@!L~JFR+!wc|IP90cM`N$d^toW6z1L z2|khC(Rt3eV}voT%Q4a|f-^QUMfRF5a?Ob%Z-c+Z(lX&T zj1!GD13cfVg!678p$#fjvT`XA&yh;+T#4MhOvc=Oo{a5kk(v$XOU*AYkX<$azr0W; z0F&-q8O;uII+De){P=7-$j z4XUdOR^~=@R|@h_K33IEGW{J#H-Z+klvl1CVspXO@;WmIY`=kW zMSZ}ZdbF*9;B07^#|H36_pnk|;8dfPYk%mVQam3+O#iA(1W{HKq*xw~8&42}idr^N z32ZGMx*GKOV$xP9SI~FA6RN)xbvx5p?GJe|u*}q;@AD_pV>M#1K3HCjRm6;=4u|$7 z7Q}`*qZB&FVnXjuV;rxsDn~UmR1_bI=)}rM4X1>k>L|Q*dGv#G$4E~oc14v!{ur_O z$jpz`hMWjx3&A$hpk{uSP#^AZp@o{CzURaEui9*{{G3?Bj0O##nBojvedV;1ML^$X z@;-}cB-F){F~oD8@^h;p<68XO%D+#o3{4^~FlLeAJ?h=xmC?LM?ZQ3kh87uT?ooMv zdQYpmKb>;#N|~C!NM>woli9dG-Lrd@% ztx>aP)4)7PC^nX7m6>2+^=^O)>K})yiI#;I=ul{@dL_kMB;9}Ln5mBV{vmP{jYlbo z`ueGsg%|A5tdo*eWmS(}%(E(_<|ykkAn+nok2Wgy^j~FssM={WJe`$H|0nMq{BufS+!0phO&RBuQK55670xK*@f>~f zy(^>+n3lg-Vt9VO$9;Hy?w?KO-+!4T9==xge`GbDm+N!LS8xtF9OsZ@@r?Y$XK#~J zpAXI)XXDJ_;F);K%ekF)Ov{d!Bk*a80M@4wcBj%T=&JpA_2fwHE-2SxOPjI_cdhzL zEHEfmO&PzN)RKd8r66NfjX~{1)K}HYXuct>tCl*o@l@4I9iFR<;pVD0n(?UL6%A=q z*~7;q|EZQ=t}@BgZ!-pd5j61j-Tks3MR{t(08q>c1mS z|EOG7dUC1X;Cnm$1>{m&MwH+7cK4M1_al%?Rbbw{1ia~^7j!eQWmI zw;Mp80^UPDRy3ZuR5kc}0EeRP9MD^U7m@!9sGeJ;=1M6Z=jCAj_WSQSGoj}?*#9Fi zP-n6KMV$#1a;aU>_9)={(Df?l<|18#{nXdgVE(rIpG(!i*5$yTfbg8)e7O}k1IPl8 z01p8hfSZ99;1FO!s0Tqdm#ReFKENda=ii4wSpQJ@ZS&uAnxOMV6{8^LPojJ@`2GV( zxHRFWrNFFqcb>M*Tw2TS5N>3>@Eq>)G)B=TgT)&->76+Wr~&6nMBV z3MJ2}1>Xbz4&bLi9_8PH4xHzVC)e3!0H14?PM$Lj^{XJ;3p%p-lS?f|`@aEZ4BtZD z|I#na=2A9rEI?n`uUL^Yxzs4|<^kseuL4Gop}zs|c%X3OSJa+`Txtq%9q?Dcv@>+; z@%Nl>qTkwgJ&wOanXmhGV%~EeRXTNiIByq%_ch=+;2@Ow09q%`bIYNG?mZ=|=MCpiIPM3dPLHdue;8%@%lV@9>ps7a@_Ha% z@X2$kAa@~n1LybK;2#bCP-788{r>=_--G>U%(zAyKeT_rIYVE|ycX#v$MJ`tcpQMV z_3M{oe=X#K`OZFh&a>c~>xw>c9la9Nd<{x>o7bA>dKDu2g|F2g%S+B7HRDTyJ zm--CyzYf)RBF}diI??~2ooOGYPov)d;tZPTHTkv@ZD?}>U;*6w9tXmDOupEE&q+Y; zujs3p1DyZiIsZLXM_r!t2cY;HE~Voi9&hU98nCbFGV;7G+5o-o2XkW+c#MzU+XBA! zP5Th|?>QBazYp@kczVvwAv+DI56{_Ug*uZN?Egz>vm3yDEzp}wu}?uCIOjd*oPw{# zvf*|E^MU`l)WOOI+iLLawYPB%`Ab9foX`3zQw0HJisui)LlGyXq`e6S`_&U3yF-cWPC^!(S?`VzF&X9YdRoC7Br^NQwD{|H`qJ)q1c zzyo+rEB1$tv~I3{VcpN8yxRcXUj*-6z|7&{Xb^v+D3?ki2Co37pMOLCV(1Lw$u*N_ zH+{Xc-pIy`ngaEl&!YYgwEr+9_W<(6YnSI72AS}lXmpyf8hZW|3?jp`nCD~w-G5E* zL3xj`7|L9qr-7aTaNqe?&_%#8fQvHy)cwG5u?~(h1&-(;@JrwKu`2070$)(1?KI3@{zW^8B5deBN#3@66;^xOHs5BJGYPwIl9mO;_43%XZs} zJgI1E<)SUu7~VeLFL@51;dKIU0;d#w_8fi}VJ`3ufbTo*1O5z{=S{=Wt=spU2=tr_ z@SP0xu)lW$mjZmw-U=)R_65d-e4@W!1=a%p4GiT&Y5RO{$@ieT?K_~$0RC?i#=q3~ z^O@RBK=A%V%Z>%_c!0V}jn!cL`W-CeOuwH2^z&8EnS=c0!0Es=U^Z|ha08&9@#^Pa zIh5xrU(JVpm-A)t9|ywmD7LTl{??ay8kEm`ZGdCTXSRICcr|bZz%e)s7zNY-D*(P* zrT#wPd?0wft>Zym4q)EL1@`rLKY}{8XFMAKzJpo=kjGfe0tUwaMeq($UDfpuqD*;$ z&!>ZD;-0e$%8a3Tz8<#U3chK#*w=RwcpSI40R4Zh2HR&FzT3D6;4{?Gz-qvZcMtOW z0H*=eV{DLW`<@d+p0OqGIe>Z?Q#1FMn4ttO-2Y#od>g=aw8uKej93on=b_p^<4-C3 z*F)Cu|Hbe75}(Jug=1*!bIwf%Xe$jETWuj+#M_dsgwa~)d;&{y{3Y~KftR|v z{lJd{pu+=rt#VJf7ijmvHLn%;GN8ZL;5oY^&v^U=C}pb@Z|n90KW0H6uccoBZv&^Q zDt(>LK>0X;v8IouYz)O4-oEjJRL?2)k?R823y#rH`iHCAKKt4SUVp>s-PZb%*gqTg zfAtaJgm8Gv`EmsQKSn@5Zy4~|BUZnUEn~&%lV+vw)_XeH^*!F&474QXs z_aCpQJRSC&gMklKdwsw5KFU*pXMt}4hXeGH=QP^i6QEtjgl96npVPiRS5W^n;E%u| z0Ne9BB~gHT<9`CY&$t$#UJbUtE5-h90;r#RH|1CR`IA6H#Gg8t)+`_wy n^V+bu?X(4^$<9j6|6A=t2Z%i20_LZ4nPqZ;9)L2Y4e9>^moyUg literal 0 HcmV?d00001 diff --git a/src/uis/nvcf-ui/ui/public/mockServiceWorker.js b/src/uis/nvcf-ui/ui/public/mockServiceWorker.js new file mode 100644 index 000000000..03816fc96 --- /dev/null +++ b/src/uis/nvcf-ui/ui/public/mockServiceWorker.js @@ -0,0 +1,366 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.13.6' +const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() + + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been terminated (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse( + event, + client, + requestId, + requestInterceptedAt, + ) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + interceptedAt: requestInterceptedAt, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/src/uis/nvcf-ui/ui/scripts/vendor-kui-css.js b/src/uis/nvcf-ui/ui/scripts/vendor-kui-css.js new file mode 100644 index 000000000..ccc04e54d --- /dev/null +++ b/src/uis/nvcf-ui/ui/scripts/vendor-kui-css.js @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { createRequire } from "node:module"; +import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; + +const require = createRequire(import.meta.url); +const { version } = require("@nvidia/foundations-react-core/package.json"); +const outDir = join(import.meta.dirname, "../vendor/kui-foundations"); +const versionFile = join(outDir, ".version"); +const files = ["base-external.css", "components.css"]; + +if ( + existsSync(versionFile) && + readFileSync(versionFile, "utf8").trim() === version && + files.every((f) => existsSync(join(outDir, f))) +) { + console.log(`KUI foundations CSS v${version} already up to date`); + process.exit(0); +} + +const cdn = `https://webassets.nvidia.com/kaizen-ui-foundations/${version}`; +mkdirSync(outDir, { recursive: true }); + +await Promise.all( + files.map(async (file) => { + const res = await fetch(`${cdn}/${file}`); + if (!res.ok) throw new Error(`${file}: ${res.status}`); + writeFileSync(join(outDir, file), await res.text()); + }), +); + +writeFileSync(versionFile, version); +console.log(`Vendored KUI foundations CSS v${version}`); diff --git a/src/uis/nvcf-ui/ui/src/components/AsyncBoundary.tsx b/src/uis/nvcf-ui/ui/src/components/AsyncBoundary.tsx new file mode 100644 index 000000000..d6a04f20d --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/AsyncBoundary.tsx @@ -0,0 +1,47 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { QueryErrorResetBoundary } from "@tanstack/react-query"; +import { type ReactNode, Suspense } from "react"; +import { ErrorBoundary, type FallbackProps } from "react-error-boundary"; + +interface AsyncBoundaryProps { + fallback: ReactNode; + errorFallback: (props: FallbackProps) => ReactNode; + children: ReactNode; +} + +/** + * Composes QueryErrorResetBoundary + ErrorBoundary + Suspense so that + * per-component error reset, catch, and loading states are all isolated. + * Use this as the base for any async component boundary. + */ +export function AsyncBoundary({ + fallback, + errorFallback, + children, +}: AsyncBoundaryProps) { + return ( + + {({ reset }) => ( + + {children} + + )} + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/CodeSnippet.tsx b/src/uis/nvcf-ui/ui/src/components/CodeSnippet.tsx new file mode 100644 index 000000000..fcefda5b7 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/CodeSnippet.tsx @@ -0,0 +1,62 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CodeSnippetActions, + CodeSnippetCode, + type CodeSnippetCodeProps, + type CodeSnippetLanguage, + CodeSnippetRoot, + type CodeSnippetRootProps, +} from "@nvidia/foundations-react-core"; +import type { ReactNode } from "react"; +import { CopyButton } from "./CopyButton"; + +interface CodeSnippetProps { + value: CodeSnippetCodeProps["value"]; + language?: CodeSnippetLanguage; + kind?: CodeSnippetRootProps["kind"]; + slotActions?: ReactNode; + className?: string; +} + +/** + * This composes KUI's `CodeSnippet` primitives + * so we can supply our own {@link CopyButton}. KUI's built-in + * `CodeSnippetCopyButton` calls `navigator.clipboard.writeText` directly, which + * is `undefined` in insecure (HTTP) contexts; our copy button falls back to + * `execCommand` so copy works regardless of the serving scheme. + */ +export function CodeSnippet({ + value, + language, + kind = "block", + slotActions, + className, +}: CodeSnippetProps) { + return ( + + {kind === "block" ? ( + + {slotActions} + + + ) : null} + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/CopyButton.tsx b/src/uis/nvcf-ui/ui/src/components/CopyButton.tsx new file mode 100644 index 000000000..e82ded1a0 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/CopyButton.tsx @@ -0,0 +1,86 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Button, + type ButtonProps, + Tooltip, +} from "@nvidia/foundations-react-core"; +import { Check, Copy } from "lucide-react"; +import { useRef } from "react"; +import { copyToClipboard } from "~/utils/clipboard"; + +interface CopyButtonProps { + /** The text to copy to the clipboard. */ + value: string; + /** Accessible label for the button. */ + ariaLabel?: string; + /** Tooltip shown on hover. */ + tooltip?: string; + size?: ButtonProps["size"]; + className?: string; + iconSize?: number; +} + +/** + * Icon button that copies `value` to the clipboard, with a brief copy→check + * animation on success. Copies via {@link copyToClipboard}, which falls back to + * `execCommand` in insecure (HTTP) contexts where `navigator.clipboard` is + * unavailable. + */ +export function CopyButton({ + value, + ariaLabel = "Copy to clipboard", + tooltip = "Copy", + size = "tiny", + className, + iconSize = 12, +}: CopyButtonProps) { + const ref = useRef(null); + + return ( + + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/CopyText.tsx b/src/uis/nvcf-ui/ui/src/components/CopyText.tsx new file mode 100644 index 000000000..c65d1e495 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/CopyText.tsx @@ -0,0 +1,33 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Text, type TextProps } from "@nvidia/foundations-react-core"; +import { CopyButton } from "./CopyButton"; + +interface CopyTextProps { + children: string; + kind?: TextProps["kind"]; +} + +export function CopyText({ children, kind }: CopyTextProps) { + return ( +
+ {children} + +
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/DataTable/ActiveFilters.tsx b/src/uis/nvcf-ui/ui/src/components/DataTable/ActiveFilters.tsx new file mode 100644 index 000000000..dd5c259f5 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/DataTable/ActiveFilters.tsx @@ -0,0 +1,77 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, Dropdown, Group, Tag } from "@nvidia/foundations-react-core"; +import { X } from "lucide-react"; +import { useDataTable } from "./DataTableContext"; +import { buildFilterItems, getColumnLabel } from "./utils"; + +export function ActiveFilters() { + const table = useDataTable("ActiveFilters"); + const columnFilters = table.getState().columnFilters; + + if (columnFilters.length === 0) return null; + + const activeFilters = columnFilters.flatMap((columnFilter) => { + const column = table.getColumn(columnFilter.id); + if (!column) return []; + + const { filterVariant, filterOptions = [] } = column.columnDef.meta ?? {}; + if (!filterVariant) return []; + + const columnName = getColumnLabel(column); + + const resolveLabel = (value: string) => + filterOptions.find((o) => o.value === value)?.label ?? value; + + const displayValue = Array.isArray(columnFilter.value) + ? (columnFilter.value as string[]).map(resolveLabel).join(", ") + : resolveLabel(columnFilter.value as string); + + return [{ id: columnFilter.id, column, columnName, displayValue }]; + }); + + return ( +
+ {activeFilters.map((activeFilter) => ( + + + + {activeFilter.columnName}: + {activeFilter.displayValue} + + + activeFilter.column.setFilterValue(undefined)} + > + + + + ))} + +
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/DataTable/ColumnVisibility.tsx b/src/uis/nvcf-ui/ui/src/components/DataTable/ColumnVisibility.tsx new file mode 100644 index 000000000..16a76b8f7 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/DataTable/ColumnVisibility.tsx @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Dropdown, + type DropdownCheckboxItemEntry, +} from "@nvidia/foundations-react-core"; +import { Columns3 } from "lucide-react"; +import { useDataTable } from "./DataTableContext"; +import { getColumnLabel } from "./utils"; + +export function ColumnVisibility() { + const table = useDataTable("ColumnVisibility"); + + const hideableColumns = table + .getAllColumns() + .filter((col) => col.getCanHide()); + + if (hideableColumns.length === 0) return null; + + const items: DropdownCheckboxItemEntry[] = hideableColumns.map((col) => ({ + kind: "checkbox", + children: getColumnLabel(col), + checked: col.getIsVisible(), + onCheckedChange: (checked) => col.toggleVisibility(!!checked), + })); + + return ( + + + Columns + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/DataTable/Content.tsx b/src/uis/nvcf-ui/ui/src/components/DataTable/Content.tsx new file mode 100644 index 000000000..32dcf73b7 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/DataTable/Content.tsx @@ -0,0 +1,201 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Button, + StatusMessage, + TableBody, + TableDataCell, + TableHead, + TableHeaderCell, + TableRoot, + TableRow, +} from "@nvidia/foundations-react-core"; +import { + flexRender, + type Header, + type Row, + type RowData, + type SortDirection, + type Table, +} from "@tanstack/react-table"; +import clsx from "clsx"; +import { + ArrowDown, + ArrowUp, + ArrowUpDown, + CircleSlash, + SearchX, +} from "lucide-react"; +import { type CSSProperties, memo, type ReactNode, useMemo } from "react"; +import { useDataTable } from "./DataTableContext"; + +function SortIcon({ sorted }: { sorted: false | SortDirection }) { + if (sorted === "asc") return ; + if (sorted === "desc") return ; + return ; +} + +function HeaderCell({ header }: { header: Header }) { + const canSort = header.column.getCanSort(); + const sorted = header.column.getIsSorted(); + const canResize = header.column.getCanResize(); + + const content = header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext()); + + return ( + + {canSort ? ( + + ) : ( + content + )} + {canResize && ( + + + )} + +
{renderValue(value)}
+ + ))} + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/NotFound.tsx b/src/uis/nvcf-ui/ui/src/components/NotFound.tsx new file mode 100644 index 000000000..2a30fc38b --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/NotFound.tsx @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, StatusMessage } from "@nvidia/foundations-react-core"; +import { Link } from "@tanstack/react-router"; +import { FileQuestionIcon } from "lucide-react"; + +export function NotFound() { + return ( +
+ + + Go to dashboard + + + } + slotHeading="Page not found" + slotMedia={} + /> +
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/OverflowGroup.test.tsx b/src/uis/nvcf-ui/ui/src/components/OverflowGroup.test.tsx new file mode 100644 index 000000000..d900c46e9 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/OverflowGroup.test.tsx @@ -0,0 +1,77 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import { OverflowGroup } from "./OverflowGroup"; + +// In tests, offsetWidth is 0 for all elements but gap (8px default) is still +// applied, so only the first child fits. This makes overflow behavior +// deterministic: N items → visibleCount = 1, "+N-1" overflow button shown. + +describe("OverflowGroup", () => { + it("renders children in the visible container", () => { + render( + + Only Item + , + ); + const visible = screen + .getAllByText("Only Item") + .find((el) => !el.closest("[aria-hidden]")); + expect(visible).toBeInTheDocument(); + expect(screen.queryByText(/^\+/)).not.toBeInTheDocument(); + }); + + it("shows an overflow button for items that do not fit", () => { + render( + + Item 1 + Item 2 + Item 3 + , + ); + expect(screen.getByText("+2")).toBeInTheDocument(); + }); + + it("expands to show all items when overflow button is clicked", async () => { + render( + + Item 1 + Item 2 + Item 3 + , + ); + await userEvent.click(screen.getByText("+2")); + expect(screen.getByText("See Less")).toBeInTheDocument(); + }); + + it("collapses back when See Less is clicked", async () => { + render( + + Item 1 + Item 2 + Item 3 + , + ); + await userEvent.click(screen.getByText("+2")); + await userEvent.click(screen.getByText("See Less")); + expect(screen.queryByText("See Less")).not.toBeInTheDocument(); + expect(screen.getByText("+2")).toBeInTheDocument(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/components/OverflowGroup.tsx b/src/uis/nvcf-ui/ui/src/components/OverflowGroup.tsx new file mode 100644 index 000000000..0f3576dcb --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/OverflowGroup.tsx @@ -0,0 +1,179 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, Popover } from "@nvidia/foundations-react-core"; +import { + Children, + type ReactNode, + useCallback, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; +import { observe, unobserve } from "~/utils/resize-observer"; + +interface OverflowGroupProps { + children: ReactNode; + className?: string; + kind?: "expand" | "popover"; + lines?: number; + gap?: number; +} + +export function OverflowGroup({ + children, + className, + kind = "expand", + lines = 1, + gap = 8, +}: OverflowGroupProps) { + const containerRef = useRef(null); + const measureRef = useRef(null); + const [visibleCount, setVisibleCount] = useState(null); + const [expanded, setExpanded] = useState(false); + const [buttonRef, setButtonRef] = useState(null); + + const childrenArray = useMemo(() => Children.toArray(children), [children]); + + const visibleChildren = useMemo( + () => + expanded + ? childrenArray + : visibleCount === null + ? [] + : childrenArray.slice(0, visibleCount), + [childrenArray, expanded, visibleCount], + ); + + const hiddenChildren = useMemo( + () => (visibleCount === null ? [] : childrenArray.slice(visibleCount)), + [childrenArray, visibleCount], + ); + + const calculate = useCallback(() => { + if (!containerRef.current || !measureRef.current || expanded) return; + + const containerWidth = containerRef.current.offsetWidth; + const items = Array.from(measureRef.current.children) as HTMLElement[]; + const buttonWidth = buttonRef?.offsetWidth ?? 0; + + let currentLine = 1; + let currentLineWidth = 0; + let count = 0; + + for (let i = 0; i < items.length; i++) { + const childWidth = items[i].offsetWidth + gap; + const needsButton = count < items.length - 1; + const reservedWidth = needsButton ? buttonWidth : 0; + + if ( + currentLineWidth + childWidth + reservedWidth > containerWidth && + currentLineWidth > 0 + ) { + currentLine++; + currentLineWidth = childWidth; + } else { + currentLineWidth += childWidth; + } + + if (currentLine > lines) break; + count++; + } + + setVisibleCount(count); + }, [expanded, gap, lines, buttonRef]); + + useLayoutEffect(() => { + const container = containerRef.current; + if (!container) return; + + calculate(); + observe(container, calculate); + return () => unobserve(container); + }, [calculate]); + + const overflowButton = hiddenChildren.length > 0 && ( + + ); + + return ( +
0}> +
1 || expanded ? "wrap" : undefined, + visibility: visibleCount !== null || expanded ? "visible" : "hidden", + }} + > + {visibleChildren} + {kind === "popover" && overflowButton ? ( + + {hiddenChildren} +
+ } + > + {overflowButton} + + ) : ( + overflowButton + )} + {expanded && ( + + )} +
+ + {/* Hidden measurement container */} + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/RouteErrorFallback.tsx b/src/uis/nvcf-ui/ui/src/components/RouteErrorFallback.tsx new file mode 100644 index 000000000..6747827d1 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/RouteErrorFallback.tsx @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, StatusMessage } from "@nvidia/foundations-react-core"; +import { type ErrorComponentProps, useRouter } from "@tanstack/react-router"; +import { AlertTriangleIcon } from "lucide-react"; + +export function RouteErrorFallback({ error }: ErrorComponentProps) { + const router = useRouter(); + return ( +
+ router.invalidate()}> + Try again + + } + slotHeading={error.message || "Something went wrong"} + slotMedia={} + /> +
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/components/RouteSpinner.tsx b/src/uis/nvcf-ui/ui/src/components/RouteSpinner.tsx new file mode 100644 index 000000000..b52b92506 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/components/RouteSpinner.tsx @@ -0,0 +1,27 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Spinner } from "@nvidia/foundations-react-core"; + +/** Centered route-level loading spinner (the router's `defaultPendingComponent`). */ +export function RouteSpinner() { + return ( +
+ +
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/config/.gitkeep b/src/uis/nvcf-ui/ui/src/config/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.test.tsx b/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.test.tsx new file mode 100644 index 000000000..7cd754cc6 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.test.tsx @@ -0,0 +1,116 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRoute } from "@tanstack/react-router"; +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it } from "vitest"; +import { getGetCloudAccountsMockHandler } from "~/generated/api/account/account.msw"; +import type { AccountDto } from "~/generated/model/accountDto"; +import { server } from "~/mocks/server"; +import { rootRoute } from "~/rootRoute"; +import { renderWithRouter } from "~/testing/render"; + +const STORAGE_KEY = "nvcf-active-nca-id"; + +function account(ncaId: string, name: string): AccountDto { + return { + ncaId, + name, + maxFunctionsAllowed: 0, + maxTasksAllowed: 0, + maxTelemetriesAllowed: 0, + maxRegistryCredentialsAllowed: 0, + }; +} + +// AccountSwitcher lives in the root AppShell, so any child route renders it. +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + component: () => null, +}); + +// An account-specific page that should send the user home on a switch. +const detailRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/detail", + staticData: { account: { redirectOnChange: "/" } }, + component: () => null, +}); + +beforeEach(() => { + localStorage.clear(); + server.use( + getGetCloudAccountsMockHandler({ + cloudAccounts: [account("nca-1", "NVIDIA"), account("nca-2", "Gaia")], + }), + ); +}); + +describe("AccountSwitcher", () => { + it("defaults to the first account when nothing is stored", async () => { + renderWithRouter({ routes: [indexRoute], initialLocation: "/" }); + expect( + await screen.findByRole("menuitemradio", { name: "NVIDIA" }), + ).toBeChecked(); + }); + + it("reflects the stored account", async () => { + localStorage.setItem(STORAGE_KEY, JSON.stringify("nca-2")); + renderWithRouter({ routes: [indexRoute], initialLocation: "/" }); + expect( + await screen.findByRole("menuitemradio", { name: "Gaia" }), + ).toBeChecked(); + }); + + it("persists the selection when switched", async () => { + const user = userEvent.setup(); + renderWithRouter({ routes: [indexRoute], initialLocation: "/" }); + + await user.click( + await screen.findByRole("menuitemradio", { name: "Gaia" }), + ); + + expect(localStorage.getItem(STORAGE_KEY)).toBe(JSON.stringify("nca-2")); + expect( + await screen.findByRole("menuitemradio", { name: "Gaia" }), + ).toBeChecked(); + }); + + it("falls back to the first account when the stored id is stale", async () => { + localStorage.setItem(STORAGE_KEY, JSON.stringify("Valisthea")); + renderWithRouter({ routes: [indexRoute], initialLocation: "/" }); + expect( + await screen.findByRole("menuitemradio", { name: "NVIDIA" }), + ).toBeChecked(); + }); + + it("redirects when switching accounts on pages with accounts.redirectOnChange", async () => { + const user = userEvent.setup(); + const { router } = renderWithRouter({ + routes: [indexRoute, detailRoute], + initialLocation: "/detail", + }); + + await user.click( + await screen.findByRole("menuitemradio", { name: "Gaia" }), + ); + + await waitFor(() => expect(router.state.location.pathname).toBe("/")); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.tsx b/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.tsx new file mode 100644 index 000000000..492d26144 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcher.tsx @@ -0,0 +1,58 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Avatar, Dropdown, Text } from "@nvidia/foundations-react-core"; +import { useActiveAccount } from "../hooks/useActiveAccount"; + +export function AccountSwitcher() { + const { accounts, activeAccount, switchAccount } = useActiveAccount(); + + if (accounts.length === 0) { + return null; + } + + return ( + ({ + value: account.ncaId, + children: account.name, + })), + }, + ]} + size="small" + > +
+ + + {activeAccount?.name} + +
+
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcherSkeleton.tsx b/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcherSkeleton.tsx new file mode 100644 index 000000000..91b368a94 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/accounts/components/AccountSwitcherSkeleton.tsx @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Dropdown, Skeleton } from "@nvidia/foundations-react-core"; + +export function AccountSwitcherSkeleton() { + return ( + +
+ + +
+
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/accounts/getActiveNcaId.ts b/src/uis/nvcf-ui/ui/src/features/accounts/getActiveNcaId.ts new file mode 100644 index 000000000..8be6eae8d --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/accounts/getActiveNcaId.ts @@ -0,0 +1,31 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { QueryClient } from "@tanstack/react-query"; +import { getGetCloudAccountsQueryOptions } from "~/generated/api/account/account"; +import { resolveActiveAccount } from "./hooks/useActiveAccount"; + +export async function getActiveNcaId( + queryClient: QueryClient, +): Promise { + const response = await queryClient.ensureQueryData( + getGetCloudAccountsQueryOptions(), + ); + const account = resolveActiveAccount(response.cloudAccounts ?? []); + if (!account) throw new Error("No account found"); + return account.ncaId; +} diff --git a/src/uis/nvcf-ui/ui/src/features/accounts/hooks/useActiveAccount.ts b/src/uis/nvcf-ui/ui/src/features/accounts/hooks/useActiveAccount.ts new file mode 100644 index 000000000..51a442c1d --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/accounts/hooks/useActiveAccount.ts @@ -0,0 +1,78 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { type LinkProps, useRouter } from "@tanstack/react-router"; +import { useCallback, useEffect } from "react"; +import { useGetCloudAccountsSuspense } from "~/generated/api/account/account"; +import type { AccountDto } from "~/generated/model/accountDto"; +import { readLocalStorage, useLocalStorage } from "~/hooks/useLocalStorage"; + +const STORAGE_KEY = "nvcf-active-nca-id"; + +/** + * Return active account from storage or first account in list if no stored value exists. + */ +export function resolveActiveAccount( + accounts: AccountDto[], +): AccountDto | undefined { + const storedNcaId = readLocalStorage(STORAGE_KEY, null); + return ( + accounts.find((account) => account.ncaId === storedNcaId) ?? accounts[0] + ); +} + +export function useActiveAccount() { + const router = useRouter(); + const { data } = useGetCloudAccountsSuspense(); + const accounts = data?.cloudAccounts ?? []; + + const [activeNcaId, setActiveNcaId] = useLocalStorage( + STORAGE_KEY, + null, + ); + const activeAccount = + accounts.find((account) => account.ncaId === activeNcaId) ?? accounts[0]; + + // Keep storage in sync: initialise on first load (null) and correct stale IDs. + useEffect(() => { + if (accounts.length > 0 && activeAccount?.ncaId !== activeNcaId) { + setActiveNcaId(activeAccount?.ncaId ?? null); + } + }, [accounts, activeNcaId, activeAccount, setActiveNcaId]); + + const switchAccount = useCallback( + async (ncaId: string) => { + setActiveNcaId(ncaId); + + const redirectTo = router.state.matches.findLast( + (match) => match.staticData.account?.redirectOnChange, + )?.staticData.account?.redirectOnChange; + + if (redirectTo) { + await router.navigate({ + to: redirectTo as LinkProps["to"], + viewTransition: true, + }); + } else { + await router.invalidate(); + } + }, + [router, setActiveNcaId], + ); + + return { accounts, activeAccount, switchAccount }; +} diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.test.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.test.tsx new file mode 100644 index 000000000..c3cbae127 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.test.tsx @@ -0,0 +1,162 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from "@testing-library/react"; +import { beforeAll, describe, expect, it } from "vitest"; +import { getGetClusterForNcaIdAndClusterIdMockHandler } from "~/generated/api/clusters/clusters.msw"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { server } from "~/mocks/server"; +import { renderWithRouter } from "~/testing/render"; +import { clusterDetailRoute } from "./routes"; + +// TanStack Router's .lazy() uses a dynamic import() at runtime. Pre-warming it +// here via beforeAll ensures the module is in the dynamic-import cache before +// any test fires, avoiding a cold Vite transform under CI CPU pressure. +beforeAll(async () => { + await import("./ClusterDetails"); +}); + +const CLUSTER_ID = "c1"; +const baseClstr = { + clusterId: CLUSTER_ID, + clusterName: "my-test-cluster", +}; + +describe("ClusterDetails", () => { + it("renders cluster name and breadcrumb", async () => { + server.use( + getGetClusterForNcaIdAndClusterIdMockHandler({ + ...baseClstr, + status: GetClusterResponseStatus.READY, + }), + ); + + renderWithRouter({ + routes: [clusterDetailRoute], + initialLocation: `/clusters/${CLUSTER_ID}`, + }); + + const matches = await screen.findAllByText("my-test-cluster"); + expect(matches.length).toBeGreaterThan(0); + const computeMatches = await screen.findAllByText("Compute Clusters"); + expect(computeMatches.length).toBeGreaterThan(0); + }); + + it("renders overview panel fields", async () => { + server.use( + getGetClusterForNcaIdAndClusterIdMockHandler({ + ...baseClstr, + status: GetClusterResponseStatus.READY, + region: "us-east-1", + nvcaVersion: "2.0.0", + clusterGroupName: "test-group", + clusterDescription: "A test cluster", + }), + ); + + renderWithRouter({ + routes: [clusterDetailRoute], + initialLocation: `/clusters/${CLUSTER_ID}`, + }); + + const matches = await screen.findAllByText("my-test-cluster"); + expect(matches.length).toBeGreaterThan(0); + expect(screen.getByText("us-east-1")).toBeInTheDocument(); + expect(screen.getByText("2.0.0")).toBeInTheDocument(); + expect(screen.getByText("test-group")).toBeInTheDocument(); + expect(screen.getByText("A test cluster")).toBeInTheDocument(); + }); + + it("renders GPU utilization bars", async () => { + server.use( + getGetClusterForNcaIdAndClusterIdMockHandler({ + ...baseClstr, + gpuUsage: { + A100: { capacity: 10, available: 5 }, + }, + }), + ); + + renderWithRouter({ + routes: [clusterDetailRoute], + initialLocation: `/clusters/${CLUSTER_ID}`, + }); + + const matches = await screen.findAllByText("my-test-cluster"); + expect(matches.length).toBeGreaterThan(0); + expect(screen.getByText("GPU Utilization")).toBeInTheDocument(); + expect(screen.getByText("A100")).toBeInTheDocument(); + expect(screen.getByText("5/10 GPUs")).toBeInTheDocument(); + }); + + it("renders empty state in instance configuration panel when gpus are absent", async () => { + server.use( + getGetClusterForNcaIdAndClusterIdMockHandler({ + ...baseClstr, + status: GetClusterResponseStatus.NOT_READY, + }), + ); + + renderWithRouter({ + routes: [clusterDetailRoute], + initialLocation: `/clusters/${CLUSTER_ID}`, + }); + + expect( + await screen.findByText("Instance Configuration"), + ).toBeInTheDocument(); + expect( + screen.getByText("No Instance Configuration Yet"), + ).toBeInTheDocument(); + }); + + it("renders instance configuration tabs when gpus are present", async () => { + server.use( + getGetClusterForNcaIdAndClusterIdMockHandler({ + ...baseClstr, + gpus: [ + { + name: "A100", + capacity: 8, + instanceTypes: [ + { + name: "OCI.GPU.A100_1x", + value: "OCI.GPU.A100", + cpuCores: 32, + systemMemory: "251Gi", + gpuCount: 1, + gpuMemory: "80Gi", + description: "NVIDIA A100 on OCI", + }, + ], + }, + ], + }), + ); + + renderWithRouter({ + routes: [clusterDetailRoute], + initialLocation: `/clusters/${CLUSTER_ID}`, + }); + + const matches = await screen.findAllByText("my-test-cluster"); + expect(matches.length).toBeGreaterThan(0); + const gpuMatches = await screen.findAllByText("A100"); + expect(gpuMatches.length).toBeGreaterThan(0); + expect(screen.getByText("Instance Configuration")).toBeInTheDocument(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.tsx new file mode 100644 index 000000000..296c31011 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/ClusterDetails.tsx @@ -0,0 +1,237 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Breadcrumbs, + PageHeader, + Panel, + StatusMessage, + type TabItem, + Tabs, +} from "@nvidia/foundations-react-core"; +import { createLazyRoute, Link } from "@tanstack/react-router"; +import { Lightbulb } from "lucide-react"; +import { DetailList } from "~/components/DetailList"; +import { useGetClusterForNcaIdAndClusterIdSuspense } from "~/generated/api/clusters/clusters"; +import type { GetClusterResponseGpuUsage } from "~/generated/model/getClusterResponseGpuUsage"; +import type { GpuResponseSchema } from "~/generated/model/gpuResponseSchema"; +import { DETAIL_REFETCH_INTERVAL } from "~/lib/queryClient"; +import { formatDateTime } from "~/utils/formatters"; +import { GpuUtilization } from "./components/GpuUtilization"; +import { StatusBadge } from "./components/StatusBadge"; +import { clusterDetailRoute } from "./routes"; + +export const ClusterDetailsRoute = createLazyRoute("/clusters/$clusterId")({ + component: ClusterDetails, +}); + +function InstanceConfigPanel({ + gpus, + usage, +}: { + gpus: GpuResponseSchema[]; + usage: GetClusterResponseGpuUsage; +}) { + const gpuItems: TabItem[] = gpus.map((gpu) => { + const instanceTypes = gpu.instanceTypes ?? []; + + const instanceItems: TabItem[] = instanceTypes.map((instance) => ({ + value: instance.name, + children: instance.name, + slotContent: ( + + ), + })); + + return { + value: gpu.name, + children: gpu.name, + slotContent: ( +
+ + {instanceItems.length > 0 && ( + + )} +
+ ), + }; + }); + + return ( + + {gpuItems.length === 0 ? ( + } + slotSubheading="Instance configuration will appear once the cluster is ready" + /> + ) : ( + + )} + + ); +} + +function ClusterDetails() { + const { ncaId } = clusterDetailRoute.useLoaderData(); + const { clusterId } = clusterDetailRoute.useParams(); + + const { data: cluster } = useGetClusterForNcaIdAndClusterIdSuspense( + ncaId, + clusterId, + { query: { refetchInterval: DETAIL_REFETCH_INTERVAL } }, + ); + + const cfg = cluster.clusterConfigurations ?? {}; + + return ( +
+ + Compute Clusters + + ), + }, + cluster.clusterName ?? clusterId, + ]} + /> + } + slotHeading={cluster.clusterName ?? clusterId} + /> +
+
+ +
+ , + }, + { + label: "Cluster Description", + value: cluster.clusterDescription, + }, + { + label: "Cluster Agent Version", + value: cluster.nvcaVersion, + }, + { label: "Cluster Group", value: cluster.clusterGroupName }, + { label: "Region", value: cluster.region }, + { + label: "Last Updated", + value: formatDateTime(cluster.nvcaLastConnected), + }, + ]} + /> + {cluster.gpuUsage && Object.keys(cluster.gpuUsage).length > 0 && ( + + {Object.entries(cluster.gpuUsage ?? {}).map( + ([name, usage]) => ( + + ), + )} +
+ ), + }, + ]} + /> + )} +
+ + +
+ + + + +
+ + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.test.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.test.tsx new file mode 100644 index 000000000..d45520b67 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.test.tsx @@ -0,0 +1,57 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { getGetClustersMockHandler } from "~/generated/api/clusters/clusters.msw"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { server } from "~/mocks/server"; +import { renderWithRouter } from "~/testing/render"; +import { clustersListRoute } from "./routes"; + +describe("ClustersList", () => { + it("renders a list of clusters", async () => { + server.use( + getGetClustersMockHandler([ + { + clusterName: "cluster-1", + status: GetClusterResponseStatus.READY, + region: "us-east-1", + gpus: [{ name: "NVIDIA A100" }], + }, + ]), + ); + + renderWithRouter({ + routes: [clustersListRoute], + initialLocation: "/clusters", + }); + + expect(await screen.findByText("cluster-1")).toBeInTheDocument(); + }); + + it("shows empty state when no clusters are returned", async () => { + server.use(getGetClustersMockHandler([])); + + renderWithRouter({ + routes: [clustersListRoute], + initialLocation: "/clusters", + }); + + expect(await screen.findByText("No data")).toBeInTheDocument(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.tsx new file mode 100644 index 000000000..b2468e900 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/ClustersList.tsx @@ -0,0 +1,131 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Anchor, Tag } from "@nvidia/foundations-react-core"; +import { createLazyRoute, Link } from "@tanstack/react-router"; +import { createColumnHelper } from "@tanstack/react-table"; +import { DataTable } from "~/components/DataTable"; +import { OverflowGroup } from "~/components/OverflowGroup"; +import { useGetClustersSuspense } from "~/generated/api/clusters/clusters"; +import type { GetClusterResponse } from "~/generated/model/getClusterResponse"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { useTableUrlSync } from "~/hooks/useTableUrlSync"; +import { LIST_REFETCH_INTERVAL } from "~/lib/queryClient"; +import { formatDateTime } from "~/utils/formatters"; +import { ClustersPageHeading } from "./components/ClustersPageHeading"; +import { getStatusLabel, StatusBadge } from "./components/StatusBadge"; +import { clusterStatuses } from "./constants"; + +export const ClustersListRoute = createLazyRoute("/clusters")({ + component: ClustersList, +}); + +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor("clusterName", { + header: "Name", + cell: ({ row, getValue }) => ( + + + {getValue() ?? row.original.clusterId ?? "—"} + + + ), + }), + columnHelper.accessor( + (cluster) => + getStatusLabel(cluster.status || GetClusterResponseStatus.NOT_READY), + { + id: "status", + cell: ({ row }) => , + header: "Status", + filterFn: "multiSelect", + meta: { + sortType: "text", + filterVariant: "multi-select", + filterOptions: clusterStatuses.map((s) => ({ + label: getStatusLabel(s), + value: getStatusLabel(s), + })), + }, + }, + ), + columnHelper.accessor("region", { + header: "Region", + }), + columnHelper.accessor( + (cluster) => [...new Set(cluster.gpus?.flatMap((gpu) => gpu.name))], + { + header: "GPU Types", + id: "gpuTypes", + cell: ({ getValue }) => ( + + {getValue().map((gpu) => ( + + {gpu} + + ))} + + ), + enableSorting: false, + }, + ), + columnHelper.accessor("nvcaVersion", { + header: "Agent Version", + }), + columnHelper.accessor("nvcaLastConnected", { + id: "lastUpdated", + header: "Last Updated", + meta: { sortType: "date" }, + cell: ({ getValue }) => formatDateTime(getValue()), + }), +]; + +function ClustersList() { + const { ncaId } = ClustersListRoute.useLoaderData(); + const { data: clusters } = useGetClustersSuspense(ncaId, undefined, { + query: { refetchInterval: LIST_REFETCH_INTERVAL }, + }); + const search = ClustersListRoute.useSearch(); + const navigate = ClustersListRoute.useNavigate(); + const tableUrlState = useTableUrlSync(search, navigate); + return ( +
+ + + + + + + + + + + + +
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/ClustersListPending.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/ClustersListPending.tsx new file mode 100644 index 000000000..bdc182db8 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/ClustersListPending.tsx @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DataTable } from "~/components/DataTable"; +import { ClustersPageHeading } from "./components/ClustersPageHeading"; + +export function ClustersListPending() { + return ( +
+ + +
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/components/ClustersPageHeading.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/components/ClustersPageHeading.tsx new file mode 100644 index 000000000..78c480277 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/components/ClustersPageHeading.tsx @@ -0,0 +1,33 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PageHeader } from "@nvidia/foundations-react-core"; +import { Boxes } from "lucide-react"; + +export function ClustersPageHeading() { + return ( + + + Compute Clusters + + } + /> + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.test.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.test.tsx new file mode 100644 index 000000000..b41468fc5 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.test.tsx @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { GpuUtilization } from "./GpuUtilization"; + +describe("GpuUtilization", () => { + it("derives used/capacity and percentage from availability", () => { + render( + , + ); + // used = capacity - available = 6; pct = 60%. + expect(screen.getByText("6/10 GPUs")).toBeInTheDocument(); + expect(screen.getByText("60%")).toBeInTheDocument(); + }); + + it("renders 0% when capacity is zero (no divide-by-zero)", () => { + render( + , + ); + expect(screen.getByText("0/0 GPUs")).toBeInTheDocument(); + expect(screen.getByText("0%")).toBeInTheDocument(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.tsx new file mode 100644 index 000000000..7b4852516 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/components/GpuUtilization.tsx @@ -0,0 +1,52 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FormField, ProgressBar, Text } from "@nvidia/foundations-react-core"; +import type { GpuCapacity } from "~/generated/model/gpuCapacity"; + +type GpuUtilizationProps = { + name: string; + usage: GpuCapacity; +}; + +export function GpuUtilization({ name, usage }: GpuUtilizationProps) { + const capacity = usage.capacity ?? 0; + const available = usage.available ?? 0; + const used = capacity - available; + const pct = capacity > 0 ? Math.round((used / capacity) * 100) : 0; + const id = `gpu-util-${name}`; + + return ( + + {name} + {pct}% + + } + > + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.test.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.test.tsx new file mode 100644 index 000000000..7678182d4 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.test.tsx @@ -0,0 +1,31 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from "vitest"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { getStatusLabel } from "./StatusBadge"; + +describe("getStatusLabel", () => { + it("surfaces UNHEALTHY as the friendlier 'Error' label", () => { + // Non-obvious mapping: the status enum is UNHEALTHY but users see "Error". + expect(getStatusLabel(GetClusterResponseStatus.UNHEALTHY)).toBe("Error"); + }); + + it("falls back to a title-cased status when there is no override", () => { + expect(getStatusLabel(GetClusterResponseStatus.READY)).toBe("Ready"); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.tsx new file mode 100644 index 000000000..59ff81011 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/components/StatusBadge.tsx @@ -0,0 +1,80 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Badge, type BadgeProps } from "@nvidia/foundations-react-core"; +import { + CircleCheck, + CircleMinus, + CirclePause, + CircleX, + Trash2, + Wrench, +} from "lucide-react"; +import type { ComponentType } from "react"; +import type { GetClusterResponse } from "~/generated/model/getClusterResponse"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { toTitleCase } from "~/utils/formatters"; + +type StatusConfig = { + color: BadgeProps["color"]; + icon: ComponentType<{ size?: number }>; + label?: string; +}; + +const statusConfig: Record< + NonNullable, + StatusConfig +> = { + [GetClusterResponseStatus.READY]: { color: "green", icon: CircleCheck }, + [GetClusterResponseStatus.PAUSED]: { color: "blue", icon: CirclePause }, + [GetClusterResponseStatus.FAILED]: { color: "red", icon: CircleX }, + [GetClusterResponseStatus.ABANDONED]: { color: "red", icon: CircleX }, + [GetClusterResponseStatus.NOT_READY]: { color: "gray", icon: CircleMinus }, + [GetClusterResponseStatus.UNHEALTHY]: { + color: "red", + icon: CircleX, + label: "Error", + }, + [GetClusterResponseStatus.DELETED]: { color: "gray", icon: Trash2 }, + [GetClusterResponseStatus.CORDON]: { color: "purple", icon: Wrench }, + [GetClusterResponseStatus.CORDON_AND_DRAIN]: { + color: "purple", + icon: Wrench, + }, +}; + +const fallbackConfig: StatusConfig = { color: "gray", icon: CircleMinus }; + +export function getStatusLabel( + status: NonNullable, +) { + return (statusConfig[status] ?? fallbackConfig).label ?? toTitleCase(status); +} + +export function StatusBadge({ + status = GetClusterResponseStatus.NOT_READY, +}: { + status?: GetClusterResponse["status"]; +}) { + const { color, icon: Icon } = statusConfig[status] ?? fallbackConfig; + return ( + + + {getStatusLabel(status)} + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/constants.ts b/src/uis/nvcf-ui/ui/src/features/clusters/constants.ts new file mode 100644 index 000000000..cdba13d30 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/constants.ts @@ -0,0 +1,20 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; + +export const clusterStatuses = Object.values(GetClusterResponseStatus); diff --git a/src/uis/nvcf-ui/ui/src/features/clusters/routes.tsx b/src/uis/nvcf-ui/ui/src/features/clusters/routes.tsx new file mode 100644 index 000000000..23811bf67 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/clusters/routes.tsx @@ -0,0 +1,65 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRoute } from "@tanstack/react-router"; +import { baseTableSearchSchema } from "~/components/DataTable/utils/tableSearchSchema"; +import { getActiveNcaId } from "~/features/accounts/getActiveNcaId"; +import { + getGetClusterForNcaIdAndClusterIdQueryOptions, + getGetClustersQueryOptions, +} from "~/generated/api/clusters/clusters"; +import { rootRoute } from "~/rootRoute"; +import { ClustersListPending } from "./ClustersListPending"; + +const clustersSearchSchema = baseTableSearchSchema.extend({ + sort: baseTableSearchSchema.shape.sort.default([ + { id: "lastUpdated", desc: true }, + ]), +}); + +export const clustersListRoute = createRoute({ + path: "clusters", + getParentRoute: () => rootRoute, + head: () => ({ meta: [{ title: "Compute Clusters · NVCF" }] }), + validateSearch: clustersSearchSchema, + pendingComponent: ClustersListPending, + loader: async ({ context: { queryClient } }) => { + const ncaId = await getActiveNcaId(queryClient); + await queryClient.ensureQueryData({ + ...getGetClustersQueryOptions(ncaId), + revalidateIfStale: true, + }); + return { ncaId }; + }, +}).lazy(() => import("./ClustersList").then((m) => m.ClustersListRoute)); + +export const clusterDetailRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "clusters/$clusterId", + staticData: { account: { redirectOnChange: "/clusters" } }, + loader: async ({ context: { queryClient }, params }) => { + const ncaId = await getActiveNcaId(queryClient); + const cluster = await queryClient.ensureQueryData({ + ...getGetClusterForNcaIdAndClusterIdQueryOptions(ncaId, params.clusterId), + revalidateIfStale: true, + }); + return { ncaId, clusterName: cluster.clusterName }; + }, + head: ({ loaderData }) => ({ + meta: [{ title: `${loaderData?.clusterName ?? "Cluster"} · NVCF` }], + }), +}).lazy(() => import("./ClusterDetails").then((m) => m.ClusterDetailsRoute)); diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.test.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.test.tsx new file mode 100644 index 000000000..1ec9f84df --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.test.tsx @@ -0,0 +1,514 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import { getGetClustersMockHandler } from "~/generated/api/clusters/clusters.msw"; +import { getGetControlPlaneStatusMockHandler } from "~/generated/api/control-plane/control-plane.msw"; +import { getGetAllFunctionDeploymentsMockHandler } from "~/generated/api/function-deployment/function-deployment.msw"; +import { getGetAllFunctionsMockHandler } from "~/generated/api/function-management/function-management.msw"; +import { FunctionDtoStatus } from "~/generated/model/functionDtoStatus"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { server } from "~/mocks/server"; +import { renderWithRouter } from "~/testing/render"; +import { dashboardRoute } from "./routes"; + +const baseFn = { + ncaId: "nca-1", + versionId: "v-1", + healthUri: "https://example.com/health", + createdAt: "2026-01-01T00:00:00Z", + functionType: "DEFAULT" as const, + status: FunctionDtoStatus.ACTIVE, +}; + +function render() { + return renderWithRouter({ routes: [dashboardRoute], initialLocation: "/" }); +} + +describe("Dashboard", () => { + describe("Stats Panels", () => { + describe("ClusterStatsPanel", () => { + it("shows correct counts by cluster status", async () => { + server.use( + getGetClustersMockHandler([ + { clusterName: "c1", status: GetClusterResponseStatus.READY }, + { clusterName: "c2", status: GetClusterResponseStatus.READY }, + { + clusterName: "c3", + status: GetClusterResponseStatus.UNHEALTHY, + }, + ]), + ); + render(); + const readyLink = await screen.findByRole("link", { name: "Ready" }); + expect(readyLink.previousElementSibling).toHaveTextContent("2"); + const errorLink = screen.getByRole("link", { name: "Error" }); + expect(errorLink.previousElementSibling).toHaveTextContent("1"); + }); + }); + + describe("ControlPlaneStatsPanel", () => { + it("shows unhealthy count as a button when there are unhealthy components", async () => { + server.use( + getGetControlPlaneStatusMockHandler([ + { + componentName: "api", + namespace: "core", + status: "unhealthy", + timestamp: "2026-01-01T00:00:00Z", + }, + { + componentName: "worker", + namespace: "core", + status: "unhealthy", + timestamp: "2026-01-01T00:00:00Z", + }, + ]), + ); + render(); + const unhealthyButton = await screen.findByRole("button", { + name: "Unhealthy", + }); + expect(unhealthyButton.previousElementSibling).toHaveTextContent("2"); + }); + + it("opens a modal listing unhealthy components when the button is clicked", async () => { + const user = userEvent.setup(); + server.use( + getGetControlPlaneStatusMockHandler([ + { + componentName: "sis-api", + namespace: "sis", + status: "unhealthy", + timestamp: "2026-01-01T00:00:00Z", + }, + { + componentName: "ess-api", + namespace: "ess", + status: "unhealthy", + timestamp: "2026-01-01T00:00:00Z", + }, + ]), + ); + render(); + await user.click( + await screen.findByRole("button", { name: "Unhealthy" }), + ); + expect(screen.getByText("sis-api")).toBeInTheDocument(); + expect(screen.getByText("ess-api")).toBeInTheDocument(); + }); + + it("shows unhealthy count as plain text when all components are healthy", async () => { + server.use( + getGetControlPlaneStatusMockHandler([ + { + componentName: "api", + namespace: "core", + status: "healthy", + timestamp: "2026-01-01T00:00:00Z", + }, + ]), + ); + render(); + await screen.findByText("Healthy"); + expect( + screen.queryByRole("button", { name: "Unhealthy" }), + ).not.toBeInTheDocument(); + }); + }); + + describe("FunctionStatsPanel", () => { + it("counts active functions", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [ + { + ...baseFn, + id: "f1", + name: "fn-1", + status: FunctionDtoStatus.ACTIVE, + }, + { + ...baseFn, + id: "f2", + name: "fn-2", + status: FunctionDtoStatus.ACTIVE, + }, + { + ...baseFn, + id: "f3", + name: "fn-3", + status: FunctionDtoStatus.INACTIVE, + }, + ], + }), + ); + render(); + const link = await screen.findByRole("link", { + name: "Active", + }); + expect(link.previousElementSibling).toHaveTextContent("2"); + }); + it("counts total functions", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [ + { + ...baseFn, + id: "f1", + name: "fn-1", + status: FunctionDtoStatus.ACTIVE, + }, + { + ...baseFn, + id: "f2", + name: "fn-2", + status: FunctionDtoStatus.ACTIVE, + }, + { + ...baseFn, + id: "f3", + name: "fn-3", + status: FunctionDtoStatus.INACTIVE, + }, + ], + }), + ); + render(); + const link = await screen.findByRole("link", { + name: "Total", + }); + expect(link.previousElementSibling).toHaveTextContent("3"); + }); + }); + }); + + describe("RecentFunctionsList", () => { + const baseDeployment = { + deploymentId: "d-1", + ncaId: "nca-1", + functionStatus: FunctionDtoStatus.ACTIVE, + createdAt: "2026-01-01T00:00:00Z", + lastUpdatedAt: "2026-01-01T00:00:00Z", + deploymentSpecifications: [], + }; + + it("shows empty state when there are no functions", async () => { + server.use( + getGetAllFunctionsMockHandler({ functions: [] }), + getGetAllFunctionDeploymentsMockHandler({ deployments: [] }), + ); + render(); + expect( + await screen.findByText("No deployed functions yet"), + ).toBeInTheDocument(); + }); + + it("shows only the 5 most recently updated functions", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [ + { ...baseFn, id: "f1", name: "fn-oldest", versionId: "v-1" }, + { ...baseFn, id: "f2", name: "fn-2", versionId: "v-2" }, + { ...baseFn, id: "f3", name: "fn-3", versionId: "v-3" }, + { ...baseFn, id: "f4", name: "fn-4", versionId: "v-4" }, + { ...baseFn, id: "f5", name: "fn-5", versionId: "v-5" }, + { ...baseFn, id: "f6", name: "fn-newest", versionId: "v-6" }, + ], + }), + getGetAllFunctionDeploymentsMockHandler({ + deployments: [ + { + ...baseDeployment, + functionId: "f1", + functionVersionId: "v-1", + functionName: "fn-oldest", + lastUpdatedAt: "2026-01-01T00:00:00Z", + }, + { + ...baseDeployment, + functionId: "f2", + functionVersionId: "v-2", + functionName: "fn-2", + lastUpdatedAt: "2026-01-02T00:00:00Z", + }, + { + ...baseDeployment, + functionId: "f3", + functionVersionId: "v-3", + functionName: "fn-3", + lastUpdatedAt: "2026-01-03T00:00:00Z", + }, + { + ...baseDeployment, + functionId: "f4", + functionVersionId: "v-4", + functionName: "fn-4", + lastUpdatedAt: "2026-01-04T00:00:00Z", + }, + { + ...baseDeployment, + functionId: "f5", + functionVersionId: "v-5", + functionName: "fn-5", + lastUpdatedAt: "2026-01-05T00:00:00Z", + }, + { + ...baseDeployment, + functionId: "f6", + functionVersionId: "v-6", + functionName: "fn-newest", + lastUpdatedAt: "2026-01-06T00:00:00Z", + }, + ], + }), + ); + render(); + expect(await screen.findByText("fn-newest")).toBeInTheDocument(); + expect(screen.queryByText("fn-oldest")).not.toBeInTheDocument(); + }); + + it("renders function name as a link to the function detail page", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [ + { ...baseFn, id: "f1", name: "my-function", versionId: "v-1" }, + ], + }), + getGetAllFunctionDeploymentsMockHandler({ + deployments: [ + { + ...baseDeployment, + functionId: "f1", + functionVersionId: "v-1", + functionName: "my-function", + lastUpdatedAt: "2026-01-01T00:00:00Z", + }, + ], + }), + ); + render(); + const link = await screen.findByRole("link", { name: "my-function" }); + expect(link).toHaveAttribute("href", "/functions/f1/versions/v-1"); + }); + + it("shows instance types from deployment specifications", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [ + { ...baseFn, id: "f1", name: "fn-with-gpu", versionId: "v-1" }, + ], + }), + getGetAllFunctionDeploymentsMockHandler({ + deployments: [ + { + ...baseDeployment, + functionId: "f1", + functionVersionId: "v-1", + functionName: "fn-with-gpu", + deploymentSpecifications: [ + { + gpu: "A100", + backend: "AWS", + instanceType: "A100.80GB.1x", + maxInstances: 1, + minInstances: 1, + }, + { + gpu: "H100", + backend: "AWS", + instanceType: "H100.80GB.2x", + maxInstances: 1, + minInstances: 1, + }, + ], + }, + ], + }), + ); + render(); + expect(await screen.findByText("A100.80GB.1x")).toBeInTheDocument(); + expect(screen.getByText("H100.80GB.2x")).toBeInTheDocument(); + }); + + it("deduplicates repeated instance types", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [ + { ...baseFn, id: "f1", name: "fn-dup", versionId: "v-1" }, + ], + }), + getGetAllFunctionDeploymentsMockHandler({ + deployments: [ + { + ...baseDeployment, + functionId: "f1", + functionVersionId: "v-1", + functionName: "fn-dup", + deploymentSpecifications: [ + { + gpu: "A100", + backend: "AWS", + instanceType: "A100.80GB.1x", + maxInstances: 1, + minInstances: 1, + }, + { + gpu: "A100", + backend: "GCP", + instanceType: "A100.80GB.1x", + maxInstances: 1, + minInstances: 1, + }, + ], + }, + ], + }), + ); + render(); + await screen.findByText("fn-dup"); + expect(screen.getAllByText("A100.80GB.1x")).toHaveLength(1); + }); + + it("shows function tags", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [ + { + ...baseFn, + id: "f1", + name: "fn-tagged", + versionId: "v-1", + tags: ["llm", "inference"], + }, + ], + }), + getGetAllFunctionDeploymentsMockHandler({ + deployments: [ + { + ...baseDeployment, + functionId: "f1", + functionVersionId: "v-1", + functionName: "fn-tagged", + lastUpdatedAt: "2026-01-01T00:00:00Z", + }, + ], + }), + ); + render(); + expect(await screen.findByText("llm")).toBeInTheDocument(); + expect(screen.getByText("inference")).toBeInTheDocument(); + }); + }); + describe("Ready Clusters", () => { + it("displays ready cluster status, region, and GPU utilization", async () => { + server.use( + getGetClustersMockHandler([ + { + clusterId: "c1", + clusterName: "cluster-1", + status: GetClusterResponseStatus.READY, + region: "us-west-1", + gpuUsage: { + A100: { capacity: 10, available: 4 }, + V100: { capacity: 5, available: 1 }, + }, + }, + ]), + ); + render(); + const readyCardsSection = await screen.findByRole("region", { + name: "Ready Compute Clusters", + }); + await within(readyCardsSection).findByText("cluster-1"); + expect(within(readyCardsSection).getByText("Ready")).toBeInTheDocument(); + expect( + within(readyCardsSection).getByText("us-west-1"), + ).toBeInTheDocument(); + expect( + within(readyCardsSection).getByText("6/10 GPUs"), + ).toBeInTheDocument(); + expect( + within(readyCardsSection).getByRole("progressbar", { name: /a100/i }), + ).toHaveAttribute("aria-valuetext", "60%"); + expect( + within(readyCardsSection).getByText("4/5 GPUs"), + ).toBeInTheDocument(); + expect( + within(readyCardsSection).getByRole("progressbar", { name: /v100/i }), + ).toHaveAttribute("aria-valuetext", "80%"); + }); + + it("shows empty state when no clusters are ready", async () => { + server.use( + getGetClustersMockHandler([ + { clusterName: "c1", status: GetClusterResponseStatus.UNHEALTHY }, + ]), + ); + render(); + const readyCardsSection = await screen.findByRole("region", { + name: "Ready Compute Clusters", + }); + expect( + await within(readyCardsSection).findByText("No ready clusters"), + ).toBeInTheDocument(); + }); + + it("paginates GPU types within a cluster card", async () => { + const user = userEvent.setup(); + server.use( + getGetClustersMockHandler([ + { + clusterId: "c1", + clusterName: "cluster-1", + status: GetClusterResponseStatus.READY, + gpuUsage: { + A100: { capacity: 10, available: 4 }, + V100: { capacity: 5, available: 1 }, + H100: { capacity: 8, available: 8 }, + }, + }, + ]), + ); + render(); + const readyCardsSection = await screen.findByRole("region", { + name: "Ready Compute Clusters", + }); + await within(readyCardsSection).findByText("cluster-1"); + expect( + within(readyCardsSection).getByRole("progressbar", { name: /a100/i }), + ).toBeInTheDocument(); + expect( + within(readyCardsSection).getByRole("progressbar", { name: /v100/i }), + ).toBeInTheDocument(); + expect( + within(readyCardsSection).queryByRole("progressbar", { name: /h100/i }), + ).not.toBeInTheDocument(); + await user.click( + within(readyCardsSection).getByRole("tab", { name: "2" }), + ); + expect( + within(readyCardsSection).getByRole("progressbar", { name: /h100/i }), + ).toBeInTheDocument(); + expect( + within(readyCardsSection).queryByRole("progressbar", { name: /a100/i }), + ).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.tsx new file mode 100644 index 000000000..ab85b936c --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/Dashboard.tsx @@ -0,0 +1,100 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, Divider, Panel, Text } from "@nvidia/foundations-react-core"; +import { createLazyRoute, Link } from "@tanstack/react-router"; +import { BoxesIcon, ChevronRight, FunctionSquareIcon } from "lucide-react"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { getStatusLabel } from "../clusters/components/StatusBadge"; +import { ClusterStatsPanel } from "./components/ClusterStatsPanel"; +import { ControlPlaneStatsPanel } from "./components/ControlPlaneStatsPanel"; +import { FunctionStatsPanel } from "./components/FunctionStatsPanel"; +import { PageHeading } from "./components/PageHeading"; +import { ReadyClusterCards } from "./components/ReadyClustersPanel"; +import { RecentFunctionsList } from "./components/RecentFunctionsList"; + +export const Route = createLazyRoute("/")({ + component: Dashboard, +}); + +function Dashboard() { + const { ncaId } = Route.useLoaderData(); + + return ( +
+ +
+ + + +
+ +
+
+
+ + + Ready Compute Clusters + +
+ +
+ +
+ + + Recent Functions + +
+ } + slotIcon={} + > + + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/ClusterStatsPanel.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/ClusterStatsPanel.tsx new file mode 100644 index 000000000..f340d5bee --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/ClusterStatsPanel.tsx @@ -0,0 +1,147 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Anchor, Panel, Text } from "@nvidia/foundations-react-core"; +import { Link } from "@tanstack/react-router"; +import clsx from "clsx"; +import groupBy from "lodash-es/groupBy"; +import { BoxesIcon } from "lucide-react"; +import { useGetClustersSuspense } from "~/generated/api/clusters/clusters"; +import type { GetClusterResponse } from "~/generated/model/getClusterResponse"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { formatNumberCompact } from "~/utils/formatters"; +import { getStatusLabel } from "../../clusters/components/StatusBadge"; +import { StatPanelBoundary } from "./StatPanelBoundary"; + +const clustersSelect = (clusters: GetClusterResponse[]) => { + const grouped = groupBy(clusters, (c) => { + if (c.status === GetClusterResponseStatus.READY) + return GetClusterResponseStatus.READY; + if (c.status === GetClusterResponseStatus.UNHEALTHY) + return GetClusterResponseStatus.UNHEALTHY; + return GetClusterResponseStatus.NOT_READY; + }); + return { + [GetClusterResponseStatus.READY]: + grouped[GetClusterResponseStatus.READY]?.length ?? 0, + [GetClusterResponseStatus.UNHEALTHY]: + grouped[GetClusterResponseStatus.UNHEALTHY]?.length ?? 0, + [GetClusterResponseStatus.NOT_READY]: + grouped[GetClusterResponseStatus.NOT_READY]?.length ?? 0, + }; +}; + +function ClusterStatsPanelContent({ ncaId }: { ncaId: string }) { + const { data } = useGetClustersSuspense(ncaId, undefined, { + query: { select: clustersSelect }, + }); + + return ( + } + > +
+
+ + {formatNumberCompact(data[GetClusterResponseStatus.READY])} + + + + Ready + + +
+
+ 0 && "text-accent-red", + )} + kind="display/md" + > + {formatNumberCompact(data[GetClusterResponseStatus.UNHEALTHY])} + + + + Error + + +
+
+ + {formatNumberCompact(data[GetClusterResponseStatus.NOT_READY])} + + + + Not Ready + + +
+
+
+ ); +} + +export function ClusterStatsPanel({ ncaId }: { ncaId: string }) { + return ( + } + > + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/ControlPlaneStatsPanel.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/ControlPlaneStatsPanel.tsx new file mode 100644 index 000000000..40e524df2 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/ControlPlaneStatsPanel.tsx @@ -0,0 +1,115 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Anchor, Modal, Panel, Text } from "@nvidia/foundations-react-core"; +import clsx from "clsx"; +import { AlertTriangleIcon, ShipWheelIcon } from "lucide-react"; +import { useState } from "react"; +import { useGetControlPlaneStatusSuspense } from "~/generated/api/control-plane/control-plane"; +import type { ControlPlaneComponentStatus } from "~/generated/model/controlPlaneComponentStatus"; +import { formatDateTime, formatNumberCompact } from "~/utils/formatters"; +import { StatPanelBoundary } from "./StatPanelBoundary"; + +const controlPlaneSelect = (data: ControlPlaneComponentStatus[]) => ({ + healthy: data.filter((c) => c.status === "healthy").length, + unhealthy: data.filter((c) => c.status === "unhealthy").length, + unhealthyComponents: data.filter((c) => c.status === "unhealthy"), +}); + +function ControlPlaneStatsPanelContent() { + const { data } = useGetControlPlaneStatusSuspense({ + query: { select: controlPlaneSelect }, + }); + const [modalOpen, setModalOpen] = useState(false); + + return ( + } + > +
+
+ {formatNumberCompact(data.healthy)} + Healthy +
+
+ 0 && "text-accent-red")} + kind="display/md" + > + {formatNumberCompact(data.unhealthy)} + + {data.unhealthy > 0 ? ( + + + + ) : ( + Unhealthy + )} +
+
+ {data.unhealthy > 0 && ( + + + {data.unhealthy}{" "} + {data.unhealthy === 1 ? "Component" : "Components"} In Error + + } + > +
+ {data.unhealthyComponents.map((component) => ( +
+
+ {component.componentName} + + {component.namespace} + +
+ + Since {formatDateTime(component.timestamp)} + +
+ ))} +
+
+ )} +
+ ); +} + +export function ControlPlaneStatsPanel() { + return ( + } + > + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/FunctionStatsPanel.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/FunctionStatsPanel.tsx new file mode 100644 index 000000000..274a19635 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/FunctionStatsPanel.tsx @@ -0,0 +1,89 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Anchor, Panel, Text } from "@nvidia/foundations-react-core"; +import { Link } from "@tanstack/react-router"; +import { FunctionSquareIcon } from "lucide-react"; +import { useGetAllFunctionsSuspense } from "~/generated/api/function-management/function-management"; +import { FunctionDtoStatus } from "~/generated/model/functionDtoStatus"; +import type { ListFunctionsResponse } from "~/generated/model/listFunctionsResponse"; +import { formatNumberCompact } from "~/utils/formatters"; +import { StatPanelBoundary } from "./StatPanelBoundary"; + +const functionsSelect = (data: ListFunctionsResponse) => { + return { + totalActive: (data.functions ?? []).filter( + (f) => f.status === FunctionDtoStatus.ACTIVE, + ).length, + total: data.functions?.length ?? 0, + }; +}; + +function FunctionStatsPanelContent({ ncaId }: { ncaId: string }) { + const { + data: { totalActive, total }, + } = useGetAllFunctionsSuspense(ncaId, undefined, { + query: { select: functionsSelect }, + }); + + return ( + } + > +
+
+ + {formatNumberCompact(totalActive)} + + + + Active + + +
+
+ {formatNumberCompact(total)} + + + Total + + +
+
+
+ ); +} + +export function FunctionStatsPanel({ ncaId }: { ncaId: string }) { + return ( + } + > + + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/PageHeading.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/PageHeading.tsx new file mode 100644 index 000000000..5edaab2a8 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/PageHeading.tsx @@ -0,0 +1,44 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, PageHeader } from "@nvidia/foundations-react-core"; +import { ExternalLinkIcon, LayoutDashboardIcon } from "lucide-react"; + +export function PageHeading() { + return ( + + + View Documentation + + + } + slotHeading={ +
+ + Dashboard +
+ } + /> + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/ReadyClustersPanel.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/ReadyClustersPanel.tsx new file mode 100644 index 000000000..37e4b1397 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/ReadyClustersPanel.tsx @@ -0,0 +1,185 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Button, + Card, + FormField, + Pagination, + Skeleton, + StatusMessage, + Text, +} from "@nvidia/foundations-react-core"; +import { Link } from "@tanstack/react-router"; +import { AlertTriangleIcon, CircleAlertIcon } from "lucide-react"; +import { useState } from "react"; +import { AsyncBoundary } from "~/components/AsyncBoundary"; +import { GpuUtilization } from "~/features/clusters/components/GpuUtilization"; +import { useGetClustersSuspense } from "~/generated/api/clusters/clusters"; +import type { GetClusterResponse } from "~/generated/model/getClusterResponse"; +import { GetClusterResponseStatus } from "~/generated/model/getClusterResponseStatus"; +import { StatusBadge } from "../../clusters/components/StatusBadge"; + +const PAGE_SIZE = 2; + +const readyClustersSelect = (clusters: GetClusterResponse[]) => + clusters + .filter((c) => c.status === GetClusterResponseStatus.READY) + .slice(0, 3); + +function ClusterCard({ cluster }: { cluster: GetClusterResponse }) { + const [gpuPage, setGpuPage] = useState(1); + const gpuEntries = Object.entries(cluster.gpuUsage ?? {}); + const visibleGpus = gpuEntries.slice( + (gpuPage - 1) * PAGE_SIZE, + gpuPage * PAGE_SIZE, + ); + + return ( + {cluster.clusterName}} + > + {/* Stretched overlay link: the whole card navigates, but the content + (incl. the interactive GPU pagination) is a DOM sibling of the anchor, + not nested inside it — so pagination stays clickable and it's valid, + accessible HTML. */} + +
+
+ + + + + {cluster.region} + +
+
+
+ {visibleGpus.map(([name, usage]) => ( + + ))} +
+ {gpuEntries.length > PAGE_SIZE && ( + // `relative` lifts the pagination above the overlay link so its + // tabs remain clickable and don't trigger navigation. +
+ +
+ )} +
+
+
+ ); +} + +function ClusterCardSkeleton() { + return ( + }> +
+
+ {/* Status FormField: label + badge */} +
+ + +
+ {/* Region FormField: label + text */} +
+ + +
+
+
+ {/* One GPU FormField: label (name + pct side by side) / progress bar / help text */} +
+
+ + +
+ + +
+
+
+
+ ); +} + +export function ReadyClusterCards({ ncaId }: { ncaId: string }) { + return ( + ( + + Retry + + } + slotHeading="Failed to load clusters." + slotMedia={} + /> + )} + fallback={ +
+ + + +
+ } + > +
+ +
+
+ ); +} + +function ReadyClusterCardsContent({ ncaId }: { ncaId: string }) { + const { data: clusters } = useGetClustersSuspense(ncaId, undefined, { + query: { select: readyClustersSelect }, + }); + + if (!clusters.length) { + return ( +
+ } + /> +
+ ); + } + + return clusters.map((cluster) => ( + + )); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/RecentFunctionsList.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/RecentFunctionsList.tsx new file mode 100644 index 000000000..2cf83d48c --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/RecentFunctionsList.tsx @@ -0,0 +1,176 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Anchor, + Button, + Skeleton, + StatusMessage, + Tag, + Text, +} from "@nvidia/foundations-react-core"; +import { QueryErrorResetBoundary } from "@tanstack/react-query"; +import { Link } from "@tanstack/react-router"; +import dayjs from "dayjs"; +import { uniq } from "lodash-es"; +import { CircleAlertIcon } from "lucide-react"; +import { Suspense } from "react"; +import { ErrorBoundary } from "react-error-boundary"; +import { useFunctionsWithDeployments } from "~/features/functions/hooks/useFunctionsWithDeployments"; +import type { FunctionWithDeployment } from "~/features/functions/types"; +import { formatDateTime } from "~/utils/formatters"; +import { StatusBadge } from "../../functions/components/StatusBadge"; + +const RECENT_COUNT = 5; + +function selectRecent({ + functions, +}: { + functions: FunctionWithDeployment[]; +}): FunctionWithDeployment[] { + return functions + .filter((fn) => Boolean(fn.lastUpdatedAt)) + .toSorted((a, b) => dayjs(b.lastUpdatedAt).diff(dayjs(a.lastUpdatedAt))) + .slice(0, RECENT_COUNT); +} + +function RecentFunctionRow({ fn }: { fn: FunctionWithDeployment }) { + const instanceTypes = uniq( + fn.deploymentSpecifications.map((s) => s.instanceType).filter(Boolean), + ); + + return ( +
+
+
+ + + {fn.name} + + + +
+ +
+ + {fn.id} + {fn.lastUpdatedAt && ( + <> + ·Last updated{" "} + {formatDateTime(fn.lastUpdatedAt)} + + )} + + + {(instanceTypes.length > 0 || (fn.tags && fn.tags.length > 0)) && ( +
+ {instanceTypes.map((t) => ( + + {t} + + ))} + {fn.tags?.map((t) => ( + + {t} + + ))} +
+ )} +
+
+
+ ); +} + +function RecentFunctionsSkeleton() { + return ( +
+ {Array.from({ length: RECENT_COUNT }, (_, i) => ( +
+ key={i} + > +
+
+ + +
+ +
+ + +
+
+
+ ))} +
+ ); +} + +function RecentFunctionsListContent({ ncaId }: { ncaId: string }) { + const recentFunctions = useFunctionsWithDeployments(ncaId, selectRecent); + + if (recentFunctions.length === 0) { + return ( + } + /> + ); + } + + return ( +
+ {recentFunctions.map((fn) => ( + + ))} +
+ ); +} + +export function RecentFunctionsList({ ncaId }: { ncaId: string }) { + return ( + + {({ reset }) => ( + ( + + Retry + + } + slotHeading="Failed to load functions." + slotMedia={} + /> + )} + onReset={reset} + > + }> + + + + )} + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelBoundary.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelBoundary.tsx new file mode 100644 index 000000000..178e45df2 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelBoundary.tsx @@ -0,0 +1,64 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, Panel, StatusMessage } from "@nvidia/foundations-react-core"; +import { AlertTriangleIcon } from "lucide-react"; +import type { ReactNode } from "react"; +import { AsyncBoundary } from "~/components/AsyncBoundary"; +import { StatPanelSkeleton } from "./StatPanelSkeleton"; + +interface StatPanelBoundaryProps { + slotHeading: string; + slotIcon: ReactNode; + skeletonCount: number; + children: ReactNode; +} + +export function StatPanelBoundary({ + slotHeading, + slotIcon, + skeletonCount, + children, +}: StatPanelBoundaryProps) { + return ( + ( + + + Retry + + } + slotHeading="Failed to load data." + slotMedia={} + /> + + )} + fallback={ + + } + > + {children} + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelSkeleton.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelSkeleton.tsx new file mode 100644 index 000000000..ce78bac23 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/components/StatPanelSkeleton.tsx @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Panel, + type PanelProps, + Skeleton, +} from "@nvidia/foundations-react-core"; + +export function StatPanelSkeleton({ + count = 2, + slotHeading, + slotIcon, +}: { count?: number } & PanelProps) { + return ( + + } + slotIcon={slotIcon ?? } + > +
+ {Array.from({ length: count }, (_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: static skeleton list, no reordering +
+ + +
+ ))} +
+
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/dashboard/routes.tsx b/src/uis/nvcf-ui/ui/src/features/dashboard/routes.tsx new file mode 100644 index 000000000..c2bfe8157 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/dashboard/routes.tsx @@ -0,0 +1,54 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRoute } from "@tanstack/react-router"; +import { getActiveNcaId } from "~/features/accounts/getActiveNcaId"; +import { getGetClustersQueryOptions } from "~/generated/api/clusters/clusters"; +import { getGetControlPlaneStatusQueryOptions } from "~/generated/api/control-plane/control-plane"; +import { getGetAllFunctionDeploymentsQueryOptions } from "~/generated/api/function-deployment/function-deployment"; +import { getGetAllFunctionsQueryOptions } from "~/generated/api/function-management/function-management"; +import { rootRoute } from "~/rootRoute"; + +export const dashboardRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + head: () => ({ meta: [{ title: "Dashboard · NVCF" }] }), + loader: async ({ context: { queryClient } }) => { + const ncaId = await getActiveNcaId(queryClient); + + // Fire-and-forget: warm the cache so panels resolve faster, but don't + // block navigation — each panel's Suspense boundary handles its own state. + void queryClient.ensureQueryData({ + ...getGetClustersQueryOptions(ncaId), + revalidateIfStale: true, + }); + void queryClient.ensureQueryData({ + ...getGetControlPlaneStatusQueryOptions(), + revalidateIfStale: true, + }); + void queryClient.ensureQueryData({ + ...getGetAllFunctionsQueryOptions(ncaId), + revalidateIfStale: true, + }); + void queryClient.ensureQueryData({ + ...getGetAllFunctionDeploymentsQueryOptions(ncaId), + revalidateIfStale: true, + }); + + return { ncaId }; + }, +}).lazy(() => import("./Dashboard").then((d) => d.Route)); diff --git a/src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.test.tsx b/src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.test.tsx new file mode 100644 index 000000000..b22dab9ef --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.test.tsx @@ -0,0 +1,164 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from "@testing-library/react"; +import { HttpResponse, http } from "msw"; +import { describe, expect, it } from "vitest"; +import { getGetCloudAccountsMockHandler } from "~/generated/api/account/account.msw"; +import { getGetFunctionDeploymentQueryOptions } from "~/generated/api/function-deployment/function-deployment"; +import { + getGetFunctionVersionMockHandler, + getGetFunctionVersionResponseMock, +} from "~/generated/api/function-management/function-management.msw"; +import { FunctionDtoStatus } from "~/generated/model/functionDtoStatus"; +import { handlers as noDeploymentHandlers } from "~/mocks/scenarios/functions/no-deployment"; +import { server } from "~/mocks/server"; +import { renderWithRouter } from "~/testing/render"; +import { functionDetailRoute } from "./routes"; + +const ncaId = "test-nca-id"; +const functionId = "fn-1"; +const versionId = "v-1"; + +const testAccount = { + ncaId, + name: "Test Account", + maxFunctionsAllowed: 10, + maxTasksAllowed: 10, + maxTelemetriesAllowed: 10, + maxRegistryCredentialsAllowed: 10, +}; + +const baseFn = { + ...getGetFunctionVersionResponseMock().function, + id: functionId, + versionId, + name: "my-function", + status: FunctionDtoStatus.ACTIVE, +}; + +function renderDetail(fn = baseFn) { + return renderWithRouter({ + routes: [functionDetailRoute], + initialLocation: `/functions/${fn.id}/versions/${fn.versionId}`, + }); +} + +describe("FunctionDetail", () => { + it("renders the function name in the page heading", async () => { + server.use(getGetFunctionVersionMockHandler({ function: baseFn })); + + renderDetail(); + + // Name appears in both the breadcrumb and the page heading + const matches = await screen.findAllByText("my-function"); + expect(matches.length).toBeGreaterThan(0); + }); + + it("shows Container Details panel for a container function", async () => { + server.use( + getGetFunctionVersionMockHandler({ + function: { ...baseFn, helmChart: undefined }, + }), + ); + + renderDetail(); + + expect(await screen.findByText("Container Details")).toBeInTheDocument(); + expect(screen.queryByText("Helm Chart Details")).not.toBeInTheDocument(); + }); + + it("shows Helm Chart Details panel for a helm function", async () => { + server.use( + getGetFunctionVersionMockHandler({ + function: { ...baseFn, helmChart: "oci://example.com/chart" }, + }), + ); + + renderDetail(); + + expect(await screen.findByText("Helm Chart Details")).toBeInTheDocument(); + expect(screen.queryByText("Container Details")).not.toBeInTheDocument(); + }); + + it("shows static 'No Instance Type Yet' for INACTIVE status without prefetching deployment", async () => { + server.use( + getGetCloudAccountsMockHandler({ cloudAccounts: [testAccount] }), + getGetFunctionVersionMockHandler({ + function: { ...baseFn, status: FunctionDtoStatus.INACTIVE }, + }), + ); + + const { queryClient } = renderDetail(); + + expect(await screen.findByText("No Instance Type Yet")).toBeInTheDocument(); + expect( + queryClient.getQueryState( + getGetFunctionDeploymentQueryOptions(ncaId, functionId, versionId) + .queryKey, + ), + ).toBeUndefined(); + }); + + it("shows static 'No Instance Type Yet' for DELETED status without prefetching deployment", async () => { + server.use( + getGetCloudAccountsMockHandler({ cloudAccounts: [testAccount] }), + getGetFunctionVersionMockHandler({ + function: { ...baseFn, status: FunctionDtoStatus.DELETED }, + }), + ); + + const { queryClient } = renderDetail(); + + expect(await screen.findByText("No Instance Type Yet")).toBeInTheDocument(); + expect( + queryClient.getQueryState( + getGetFunctionDeploymentQueryOptions(ncaId, functionId, versionId) + .queryKey, + ), + ).toBeUndefined(); + }); + + it("shows 'No Instance Type Yet' when the function has no deployment specifications", async () => { + server.use( + getGetFunctionVersionMockHandler({ function: baseFn }), + ...noDeploymentHandlers, + ); + + renderDetail(); + + // Two sequential MSW delays (function + deployment) — extend timeout + expect( + await screen.findByText("No Instance Type Yet", {}, { timeout: 3000 }), + ).toBeInTheDocument(); + }); + + it("shows error state when the function API fails", async () => { + server.use( + http.get( + "/v2/nvcf/accounts/:ncaId/functions/:functionId/versions/:functionVersionId", + () => new HttpResponse(null, { status: 500 }), + ), + ); + + renderDetail(); + + expect( + await screen.findByRole("button", { name: /try again/i }), + ).toBeInTheDocument(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.tsx b/src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.tsx new file mode 100644 index 000000000..0b0350608 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/FunctionDetail.tsx @@ -0,0 +1,439 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Anchor, + Breadcrumbs, + PageHeader, + Panel, + Skeleton, + StatusMessage, + type TabItem, + Tabs, + Tag, + Text, +} from "@nvidia/foundations-react-core"; +import { createLazyRoute, Link } from "@tanstack/react-router"; +import { Lightbulb } from "lucide-react"; +import { Suspense } from "react"; +import { CodeSnippet } from "~/components/CodeSnippet"; +import { CopyText } from "~/components/CopyText"; +import { DetailList } from "~/components/DetailList"; +import { OverflowGroup } from "~/components/OverflowGroup"; +import { useGetFunctionDeploymentSuspense } from "~/generated/api/function-deployment/function-deployment"; +import { useGetFunctionVersionSuspense } from "~/generated/api/function-management/function-management"; +import type { GpuSpecificationDto } from "~/generated/model/gpuSpecificationDto"; +import { DETAIL_REFETCH_INTERVAL } from "~/lib/queryClient"; +import { formatDateTime } from "~/utils/formatters"; +import { StatusBadge } from "./components/StatusBadge"; +import { DEPLOYED_FUNCTION_STATUSES, FUNCTION_TYPE } from "./constants"; +import { functionDetailRoute } from "./routes"; + +export const FunctionDetailRoute = createLazyRoute( + "/functions/$functionId/versions/$versionId", +)({ + component: FunctionDetail, +}); + +function InstanceTypesPanelSkeleton() { + return ( + +
+ {/* GPU tabs (primary) */} +
+ + + +
+ {/* Instance type tabs (secondary) */} +
+ + +
+ {/* Spec detail rows */} + {Array.from({ length: 6 }).map((_, i) => ( + // biome-ignore lint: skeleton rows have no identity +
+ + +
+ ))} +
+
+ ); +} + +function SpecDetails({ spec }: { spec: GpuSpecificationDto }) { + return ( + + + Total maximum concurrency for this function is the combined + maximum concurrency across versions and their instance types.{" "} +

+ Max concurrency defines how many requests a single instance can + handle simultaneously. You can update this total max concurrency + number by adjusting per instance type concurrency in the + deployment specification. For more details, please refer to the{" "} + + documentation + + . +
+ + ), + }, + ]} + /> + ); +} + +function InstanceTypesPanel({ + ncaId, + functionId, + versionId, +}: { + ncaId: string; + functionId: string; + versionId: string; +}) { + const { data } = useGetFunctionDeploymentSuspense( + ncaId, + functionId, + versionId, + { + query: { refetchInterval: DETAIL_REFETCH_INTERVAL }, + }, + ); + const specs = data.deployment.deploymentSpecifications; + + if (specs.length === 0) { + return ( + + } + slotSubheading="Instance types will appear once function is deployed" + /> + + ); + } + + const gpuNames = [...new Set(specs.map((s) => s.gpu))]; + + const gpuItems: TabItem[] = gpuNames.map((gpu) => { + const gpuSpecs = specs.filter((s) => s.gpu === gpu); + const instanceItems: TabItem[] = gpuSpecs.map((spec) => ({ + value: spec.instanceType, + children: spec.instanceType, + slotContent: , + })); + + return { + value: gpu, + children: gpu, + slotContent: ( + + ), + }; + }); + + return ( + + + + ); +} + +function FunctionDetail() { + const { ncaId } = functionDetailRoute.useLoaderData(); + const { functionId, versionId } = functionDetailRoute.useParams(); + + const { data: fnData } = useGetFunctionVersionSuspense( + ncaId, + functionId, + versionId, + undefined, + { query: { refetchInterval: DETAIL_REFETCH_INTERVAL } }, + ); + const fn = fnData.function; + + const invocationCommand = [ + "curl --request POST \\", + ` --url "http://\${GATEWAY_ADDR}/echo" \\`, + ` --header "Host: ${fn.id}.invocation.\${GATEWAY_ADDR}" \\`, + ` --header "Authorization: Bearer \${API_KEY}" \\`, + ' --header "Content-Type: application/json" \\', + ' --data \'{"message": "hello"}\'', + ].join("\n"); + + const envContent = fn.containerEnvironment?.length + ? fn.containerEnvironment.map((e) => `${e.key}=${e.value}`).join("\n") + : undefined; + + const rateLimit = fn.rateLimit; + const globalRateLimits = rateLimit?.rateLimit + ?.split(",") + .map((r) => r.trim()); + const reconfiguredNcaIds = + rateLimit?.perNcaIdRate && Object.keys(rateLimit.perNcaIdRate).length + ? Object.entries(rateLimit.perNcaIdRate) + .map(([k, v]) => `${k}: ${v}`) + .join(", ") + : undefined; + + return ( +
+ + Functions + + ), + }, + fn.name, + ]} + /> + } + slotHeading={fn.name} + /> + +
+ + }, + { + label: "Function ID", + value: {fn.id}, + }, + { + label: "Version ID", + value: ( + {fn.versionId} + ), + }, + { + label: "Created Date", + value: formatDateTime(fn.createdAt), + }, + { + label: "Function Type", + value: fn.helmChart + ? FUNCTION_TYPE.HELM + : FUNCTION_TYPE.CONTAINER, + }, + { label: "Description", value: fn.description }, + { + label: "Tags", + value: fn.tags?.length ? ( + + {fn.tags.map((tag) => ( + + {tag} + + ))} + + ) : undefined, + }, + ]} + /> + + + + + + Invoke Command + +
+ } + value={invocationCommand} + /> + + +
+ {fn.helmChart ? ( + + m.uri ?? m.name), + }, + { + label: "Model Mount Points", + value: fn.models?.map((m) => `/config/models/${m.name}`), + }, + { + label: "Resources", + value: fn.resources?.map((r) => r.uri), + }, + { + label: "Resource Mount Points", + value: fn.resources?.map( + (m) => `/config/resources/${m.name}`, + ), + }, + { label: "Secrets", value: fn.secrets }, + { + label: "Helm Chart Overrides", + value: fn.containerArgs ? ( + + ) : undefined, + }, + ]} + /> + + ) : ( + + m.uri ?? m.name), + }, + { + label: "Model Mount Points", + value: fn.models?.map((m) => `/config/models/${m.name}`), + }, + { + label: "Resources", + value: fn.resources?.map((r) => r.uri), + }, + { + label: "Resource Mount Points", + value: fn.resources?.map( + (m) => `/config/resources/${m.name}`, + ), + }, + { label: "Secrets", value: fn.secrets }, + { + label: "Environment", + value: envContent ? ( + + ) : undefined, + }, + { + label: "Container Args", + value: fn.containerArgs ? ( + + ) : undefined, + }, + ]} + /> + + )} + + + + +
+ +
+ {DEPLOYED_FUNCTION_STATUSES.includes(fn.status) ? ( + }> + + + ) : ( + + } + slotSubheading="Instance types will appear once function is deployed" + /> + + )} + + + + +
+
+ + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.test.tsx b/src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.test.tsx new file mode 100644 index 000000000..7bf3f8cce --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.test.tsx @@ -0,0 +1,81 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from "@testing-library/react"; +import { HttpResponse, http } from "msw"; +import { describe, expect, it } from "vitest"; +import { getGetAllFunctionsMockHandler } from "~/generated/api/function-management/function-management.msw"; +import { FunctionDtoStatus } from "~/generated/model/functionDtoStatus"; +import { handlers as emptyListHandlers } from "~/mocks/scenarios/functions/empty-list"; +import { server } from "~/mocks/server"; +import { renderWithRouter } from "~/testing/render"; +import { functionsListRoute } from "./routes"; + +const baseFn = { + ncaId: "nca-1", + versionId: "v-1", + status: FunctionDtoStatus.ACTIVE, + healthUri: "https://example.com/health", + createdAt: "2026-01-01T00:00:00Z", + functionType: "DEFAULT" as const, +}; + +describe("FunctionsList", () => { + it("renders a list of functions", async () => { + server.use( + getGetAllFunctionsMockHandler({ + functions: [{ ...baseFn, id: "fn-1", name: "my-function" }], + }), + ); + + renderWithRouter({ + routes: [functionsListRoute], + initialLocation: "/functions", + }); + + expect(await screen.findByText("my-function")).toBeInTheDocument(); + }); + + it("shows empty state when no functions are returned", async () => { + server.use(...emptyListHandlers); + + renderWithRouter({ + routes: [functionsListRoute], + initialLocation: "/functions", + }); + + expect(await screen.findByText("No data")).toBeInTheDocument(); + }); + + it("shows error state when the API fails", async () => { + server.use( + http.get( + "/v2/nvcf/accounts/:ncaId/functions", + () => new HttpResponse(null, { status: 500 }), + ), + ); + + renderWithRouter({ + routes: [functionsListRoute], + initialLocation: "/functions", + }); + + expect( + await screen.findByRole("button", { name: /try again/i }), + ).toBeInTheDocument(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.tsx b/src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.tsx new file mode 100644 index 000000000..2841ef3ab --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/FunctionsList.tsx @@ -0,0 +1,125 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createLazyRoute } from "@tanstack/react-router"; +import type { ColumnDef } from "@tanstack/react-table"; +import { DataTable } from "~/components/DataTable"; +import { useTableUrlSync } from "~/hooks/useTableUrlSync"; +import { LIST_REFETCH_INTERVAL } from "~/lib/queryClient"; +import { toTitleCase } from "~/utils/formatters"; +import { FunctionCard } from "./components/FunctionCard"; +import { FunctionsPageHeading } from "./components/FunctionsPageHeading"; +import { FUNCTION_TYPE, functionStatusValues } from "./constants"; +import { useFunctionsWithDeployments } from "./hooks/useFunctionsWithDeployments"; +import type { FunctionWithDeployment } from "./types"; + +export const FunctionListRoute = createLazyRoute("/functions")({ + component: FunctionsList, +}); + +const columns: ColumnDef[] = [ + { + accessorKey: "name", + header: "Name", + meta: { sortType: "text" }, + }, + { + accessorKey: "id", + enableSorting: false, + }, + { + accessorKey: "status", + header: "Status", + enableSorting: false, + filterFn: "multiSelect", + meta: { + filterVariant: "multi-select", + filterOptions: functionStatusValues.map((s) => ({ + label: toTitleCase(s), + value: s, + })), + }, + }, + { + accessorFn: (row) => + row.helmChart ? FUNCTION_TYPE.HELM : FUNCTION_TYPE.CONTAINER, + header: "Function Type", + enableSorting: false, + meta: { + filterVariant: "single-select", + filterOptions: Object.values(FUNCTION_TYPE).map((t) => ({ + label: t, + value: t, + })), + }, + }, + { + id: "instanceTypes", + accessorFn: (row) => + row.deploymentSpecifications.map((s) => s.instanceType).filter(Boolean), + header: "Instance Types", + enableSorting: false, + }, + { + accessorKey: "tags", + enableSorting: false, + }, + { + accessorKey: "createdAt", + header: "Created", + meta: { sortType: "date" }, + }, +]; + +function FunctionsList() { + const { ncaId } = FunctionListRoute.useLoaderData(); + const { functions } = useFunctionsWithDeployments(ncaId, undefined, { + refetchInterval: LIST_REFETCH_INTERVAL, + }); + const search = FunctionListRoute.useSearch(); + const navigate = FunctionListRoute.useNavigate(); + const tableUrlState = useTableUrlSync(search, navigate); + + return ( +
+ + + + + + + + + + + renderContent={({ rows }) => ( +
+ {rows.map((row) => ( + + ))} +
+ )} + /> + +
+
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/functions/FunctionsListPending.tsx b/src/uis/nvcf-ui/ui/src/features/functions/FunctionsListPending.tsx new file mode 100644 index 000000000..b9daac8b1 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/FunctionsListPending.tsx @@ -0,0 +1,36 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DataTable } from "~/components/DataTable"; +import { FunctionCardSkeleton } from "./components/FunctionCard"; +import { FunctionsPageHeading } from "./components/FunctionsPageHeading"; + +export function FunctionsListPending() { + return ( +
+ + +
+ + + + + +
+
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.test.tsx b/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.test.tsx new file mode 100644 index 000000000..e8c3ccac7 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.test.tsx @@ -0,0 +1,91 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { describe, expect, it, vi } from "vitest"; +import type { FunctionWithDeployment } from "../types"; +import { FunctionCard } from "./FunctionCard"; + +vi.mock("@tanstack/react-router", () => ({ + Link: ({ children }: { children: ReactNode }) => <>{children}, +})); + +vi.mock("~/components/OverflowGroup", () => ({ + OverflowGroup: ({ children }: { children: ReactNode }) => <>{children}, +})); + +const base: FunctionWithDeployment = { + id: "fn-1", + ncaId: "nca-1", + versionId: "v-1", + name: "my-function", + status: "ACTIVE", + createdAt: "2026-01-01T00:00:00Z", + lastUpdatedAt: "2026-01-01T00:00:00Z", + functionType: "DEFAULT", + healthUri: "https://example.com/health", + deploymentSpecifications: [], +}; + +describe("FunctionCard", () => { + it("renders the function name", () => { + render(); + expect(screen.getByText("my-function")).toBeInTheDocument(); + }); + + it("shows Container Function when helmChart is absent", () => { + render(); + expect(screen.getByText("Container Function")).toBeInTheDocument(); + }); + + it("shows Helm Function when helmChart is present", () => { + render(); + expect(screen.getByText("Helm Function")).toBeInTheDocument(); + }); + + it("renders instance types when present", () => { + const deploymentSpecifications = [ + { + gpu: "A100", + maxInstances: 1, + minInstances: 0, + instanceType: "A100.80GB.1x", + }, + { + gpu: "H100", + maxInstances: 1, + minInstances: 0, + instanceType: "H100.80GB.2x", + }, + ]; + render(); + expect(screen.getByText("A100.80GB.1x")).toBeInTheDocument(); + expect(screen.getByText("H100.80GB.2x")).toBeInTheDocument(); + }); + + it("renders tags when present", () => { + render(); + expect(screen.getByText("gpu")).toBeInTheDocument(); + expect(screen.getByText("inference")).toBeInTheDocument(); + }); + + it("renders an em-dash for instance types and tags when absent", () => { + render(); + expect(screen.getAllByText("—")).toHaveLength(2); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.tsx b/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.tsx new file mode 100644 index 000000000..becb613c9 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionCard.tsx @@ -0,0 +1,168 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Anchor, Skeleton, Tag, Text } from "@nvidia/foundations-react-core"; +import { Link } from "@tanstack/react-router"; +import clsx from "clsx"; +import { uniq } from "lodash-es"; +import type { ReactNode } from "react"; +import { CopyText } from "~/components/CopyText"; +import { OverflowGroup } from "~/components/OverflowGroup"; +import { formatDateTime } from "~/utils/formatters"; +import type { FunctionWithDeployment } from "../types"; +import { StatusBadge } from "./StatusBadge"; + +function Field({ + label, + children, + className, +}: { + label: string; + children: ReactNode; + className?: string; +}) { + return ( +
+ + {label} + + {children} +
+ ); +} + +function FieldSkeleton({ + labelWidth, + children, + className, +}: { + labelWidth: string; + children: ReactNode; + className?: string; +}) { + return ( +
+ + {children} +
+ ); +} + +export function FunctionCardSkeleton() { + return ( +
+
+ + +
+ +
+
+ + + + + + +
+ + + + + + +
+ + +
+
+ + + + +
+
+ ); +} + +export function FunctionCard({ fn }: { fn: FunctionWithDeployment }) { + const instanceTypes = uniq( + fn.deploymentSpecifications.map((s) => s.instanceType).filter(Boolean), + ); + + return ( +
+
+ + + {fn.name} + + + + {fn.helmChart ? "Helm Function" : "Container Function"} + +
+ +
+
+ + + + + {formatDateTime(fn.createdAt)} + +
+ + + {fn.id} + + + + {instanceTypes.length > 0 ? ( + + {instanceTypes.map((type) => ( + + {type} + + ))} + + ) : ( + + )} + + + + {fn.tags && fn.tags.length > 0 ? ( + + {fn.tags.map((tag) => ( + + {tag} + + ))} + + ) : ( + + )} + +
+
+ ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionsPageHeading.tsx b/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionsPageHeading.tsx new file mode 100644 index 000000000..2a7dc4b97 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/components/FunctionsPageHeading.tsx @@ -0,0 +1,33 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PageHeader } from "@nvidia/foundations-react-core"; +import { FunctionSquare } from "lucide-react"; + +export function FunctionsPageHeading() { + return ( + + + Functions + + } + /> + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/functions/components/StatusBadge.tsx b/src/uis/nvcf-ui/ui/src/features/functions/components/StatusBadge.tsx new file mode 100644 index 000000000..03262af63 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/components/StatusBadge.tsx @@ -0,0 +1,58 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Badge, type BadgeProps } from "@nvidia/foundations-react-core"; +import { + AlertTriangle, + CircleCheck, + CircleMinus, + CircleX, + type LucideIcon, + RocketIcon, + Trash2, +} from "lucide-react"; +import type { FunctionDto } from "~/generated/model/functionDto"; +import { FunctionDtoStatus } from "~/generated/model/functionDtoStatus"; +import { toTitleCase } from "~/utils/formatters"; + +const statusConfig: Record< + FunctionDto["status"], + { color: BadgeProps["color"]; icon: LucideIcon } +> = { + [FunctionDtoStatus.ACTIVE]: { color: "green", icon: CircleCheck }, + [FunctionDtoStatus.DEPLOYING]: { color: "blue", icon: RocketIcon }, + [FunctionDtoStatus.ERROR]: { color: "red", icon: CircleX }, + [FunctionDtoStatus.INACTIVE]: { color: "gray", icon: CircleMinus }, + [FunctionDtoStatus.DELETED]: { color: "gray", icon: Trash2 }, + [FunctionDtoStatus.DEGRADED]: { color: "yellow", icon: AlertTriangle }, + [FunctionDtoStatus.DEGRADING]: { color: "yellow", icon: AlertTriangle }, +}; + +const fallbackConfig: { color: BadgeProps["color"]; icon: LucideIcon } = { + color: "gray", + icon: CircleMinus, +}; + +export function StatusBadge({ status }: { status: FunctionDto["status"] }) { + const { color, icon: Icon } = statusConfig[status] ?? fallbackConfig; + return ( + + + {toTitleCase(status)} + + ); +} diff --git a/src/uis/nvcf-ui/ui/src/features/functions/constants.ts b/src/uis/nvcf-ui/ui/src/features/functions/constants.ts new file mode 100644 index 000000000..a487b8431 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/constants.ts @@ -0,0 +1,33 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionDtoStatus } from "~/generated/model/functionDtoStatus"; + +export const functionStatusValues = Object.values(FunctionDtoStatus); + +export const DEPLOYED_FUNCTION_STATUSES: FunctionDtoStatus[] = [ + FunctionDtoStatus.ACTIVE, + FunctionDtoStatus.DEPLOYING, + FunctionDtoStatus.DEGRADED, + FunctionDtoStatus.DEGRADING, + FunctionDtoStatus.ERROR, +]; + +export const FUNCTION_TYPE = { + HELM: "Helm Function", + CONTAINER: "Container Function", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/features/functions/hooks/useFunctionsWithDeployments.ts b/src/uis/nvcf-ui/ui/src/features/functions/hooks/useFunctionsWithDeployments.ts new file mode 100644 index 000000000..a30541e73 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/hooks/useFunctionsWithDeployments.ts @@ -0,0 +1,80 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { UseSuspenseQueryResult } from "@tanstack/react-query"; +import { useSuspenseQueries } from "@tanstack/react-query"; +import { + type GetAllFunctionDeploymentsSuspenseQueryResult, + getGetAllFunctionDeploymentsSuspenseQueryOptions, +} from "~/generated/api/function-deployment/function-deployment"; +import { + type GetAllFunctionsSuspenseQueryResult, + getGetAllFunctionsSuspenseQueryOptions, +} from "~/generated/api/function-management/function-management"; +import type { FunctionWithDeployment } from "../types"; + +function combine([functionsResult, deploymentsResult]: [ + UseSuspenseQueryResult, + UseSuspenseQueryResult, +]): { + functions: FunctionWithDeployment[]; +} { + const specsMap = new Map( + deploymentsResult.data?.deployments?.map((d) => [ + d.functionVersionId, + { specs: d.deploymentSpecifications, lastUpdatedAt: d.lastUpdatedAt }, + ]), + ); + return { + functions: (functionsResult.data?.functions ?? []).map((fn) => ({ + ...fn, + deploymentSpecifications: specsMap.get(fn.versionId)?.specs ?? [], + lastUpdatedAt: specsMap.get(fn.versionId)?.lastUpdatedAt ?? "", + })), + }; +} + +type BaseCombineResult = ReturnType; + +/** Config-only query options safe to apply to both underlying queries. */ +type SharedQueryOptions = { + refetchInterval?: number; + staleTime?: number; +}; + +export function useFunctionsWithDeployments( + ncaId: string, + select?: (data: BaseCombineResult) => T, + queryOptions?: SharedQueryOptions, +) { + return useSuspenseQueries({ + queries: [ + { + ...getGetAllFunctionsSuspenseQueryOptions(ncaId), + ...queryOptions, + }, + { + ...getGetAllFunctionDeploymentsSuspenseQueryOptions(ncaId), + ...queryOptions, + }, + ], + combine(results) { + const base = combine(results); + return (select ? select(base) : base) as T; + }, + }); +} diff --git a/src/uis/nvcf-ui/ui/src/features/functions/routes.tsx b/src/uis/nvcf-ui/ui/src/features/functions/routes.tsx new file mode 100644 index 000000000..5b3397698 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/routes.tsx @@ -0,0 +1,95 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRoute } from "@tanstack/react-router"; +import { baseTableSearchSchema } from "~/components/DataTable/utils/tableSearchSchema"; +import { getActiveNcaId } from "~/features/accounts/getActiveNcaId"; +import { + getGetAllFunctionDeploymentsQueryOptions, + getGetFunctionDeploymentQueryOptions, +} from "~/generated/api/function-deployment/function-deployment"; +import { + getGetAllFunctionsQueryOptions, + getGetFunctionVersionQueryOptions, +} from "~/generated/api/function-management/function-management"; +import { rootRoute } from "~/rootRoute"; +import { DEPLOYED_FUNCTION_STATUSES } from "./constants"; +import { FunctionsListPending } from "./FunctionsListPending"; + +const functionsSearchSchema = baseTableSearchSchema.extend({ + sort: baseTableSearchSchema.shape.sort.default([ + { id: "createdAt", desc: true }, + ]), +}); + +export const functionsListRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "functions", + head: () => ({ meta: [{ title: "Functions · NVCF" }] }), + validateSearch: functionsSearchSchema, + pendingComponent: FunctionsListPending, + loader: async ({ context: { queryClient } }) => { + const ncaId = await getActiveNcaId(queryClient); + const [functionsResult] = await Promise.allSettled([ + queryClient.ensureQueryData({ + ...getGetAllFunctionsQueryOptions(ncaId), + revalidateIfStale: true, + }), + queryClient.ensureQueryData({ + ...getGetAllFunctionDeploymentsQueryOptions(ncaId), + revalidateIfStale: true, + }), + ]); + // Deployments are best-effort — only a functions failure hard-fails the + // route; the list still renders without deployment enrichment. + if (functionsResult.status === "rejected") { + throw functionsResult.reason; + } + return { ncaId }; + }, +}).lazy(() => import("./FunctionsList").then((m) => m.FunctionListRoute)); + +export const functionDetailRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "functions/$functionId/versions/$versionId", + staticData: { account: { redirectOnChange: "/functions" } }, + loader: async ({ context: { queryClient }, params }) => { + const ncaId = await getActiveNcaId(queryClient); + const data = await queryClient.ensureQueryData({ + ...getGetFunctionVersionQueryOptions( + ncaId, + params.functionId, + params.versionId, + ), + revalidateIfStale: true, + }); + const fn = data?.function; + if (fn && DEPLOYED_FUNCTION_STATUSES.includes(fn.status)) { + void queryClient.prefetchQuery( + getGetFunctionDeploymentQueryOptions( + ncaId, + params.functionId, + params.versionId, + ), + ); + } + return { ncaId, functionName: fn?.name }; + }, + head: ({ loaderData }) => ({ + meta: [{ title: `${loaderData?.functionName ?? "Function"} · NVCF` }], + }), +}).lazy(() => import("./FunctionDetail").then((m) => m.FunctionDetailRoute)); diff --git a/src/uis/nvcf-ui/ui/src/features/functions/types/index.ts b/src/uis/nvcf-ui/ui/src/features/functions/types/index.ts new file mode 100644 index 000000000..935b43cee --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/features/functions/types/index.ts @@ -0,0 +1,25 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { FunctionDeploymentDto } from "~/generated/model/functionDeploymentDto"; +import type { FunctionDto } from "~/generated/model/functionDto"; +import type { GpuSpecificationDto } from "~/generated/model/gpuSpecificationDto"; + +export type FunctionWithDeployment = FunctionDto & { + deploymentSpecifications: GpuSpecificationDto[]; + lastUpdatedAt: FunctionDeploymentDto["lastUpdatedAt"]; +}; diff --git a/src/uis/nvcf-ui/ui/src/generated/api/account/account.msw.ts b/src/uis/nvcf-ui/ui/src/generated/api/account/account.msw.ts new file mode 100644 index 000000000..f5c3aabbb --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/account/account.msw.ts @@ -0,0 +1,70 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import { faker } from "@faker-js/faker/locale/en"; +import type { RequestHandlerOptions } from "msw"; +import { delay, HttpResponse, http } from "msw"; + +import type { ListAccountResponse } from "../../model/listAccountResponse"; + +export const getGetCloudAccountsResponseMock = ( + overrideResponse: Partial> = {}, +): ListAccountResponse => ({ + cloudAccounts: (() => + Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => ({ + ncaId: faker.string.alphanumeric({ length: 13 }), + name: faker.company.name(), + maxFunctionsAllowed: faker.number.int(), + maxTasksAllowed: faker.number.int(), + maxTelemetriesAllowed: faker.number.int(), + maxRegistryCredentialsAllowed: faker.number.int(), + })))(), + ...overrideResponse, +}); + +export const getGetCloudAccountsMockHandler = ( + overrideResponse?: + | ListAccountResponse + | (( + info: Parameters[1]>[0], + ) => Promise | ListAccountResponse), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v2/nvcf/accounts", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetCloudAccountsResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; +export const getAccountMock = () => [getGetCloudAccountsMockHandler()]; diff --git a/src/uis/nvcf-ui/ui/src/generated/api/account/account.ts b/src/uis/nvcf-ui/ui/src/generated/api/account/account.ts new file mode 100644 index 000000000..a44a788bd --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/account/account.ts @@ -0,0 +1,324 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, + UseSuspenseQueryOptions, + UseSuspenseQueryResult, +} from "@tanstack/react-query"; +import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; +import { customFetch } from "../../../lib/fetch"; +import type { ListAccountResponse } from "../../model/listAccountResponse"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary List all the NVIDIA Cloud Accounts onboarded with Cloud Functions + */ +export const getGetCloudAccountsUrl = () => { + return `/v2/nvcf/accounts`; +}; + +export const getCloudAccounts = async ( + options?: RequestInit, +): Promise => { + return customFetch(getGetCloudAccountsUrl(), { + ...options, + method: "GET", + }); +}; + +export const getGetCloudAccountsQueryKey = () => { + return [`/v2/nvcf/accounts`] as const; +}; + +export const getGetCloudAccountsQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetCloudAccountsQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => getCloudAccounts({ signal, ...requestOptions }); + + return { + queryKey, + queryFn, + staleTime: Infinity, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetCloudAccountsQueryResult = NonNullable< + Awaited> +>; +export type GetCloudAccountsQueryError = unknown; + +export function useGetCloudAccounts< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetCloudAccounts< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetCloudAccounts< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List all the NVIDIA Cloud Accounts onboarded with Cloud Functions + */ + +export function useGetCloudAccounts< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetCloudAccountsQueryOptions(options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetCloudAccountsSuspenseQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetCloudAccountsQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => getCloudAccounts({ signal, ...requestOptions }); + + return { + queryKey, + queryFn, + staleTime: Infinity, + ...queryOptions, + } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetCloudAccountsSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetCloudAccountsSuspenseQueryError = unknown; + +export function useGetCloudAccountsSuspense< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetCloudAccountsSuspense< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetCloudAccountsSuspense< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List all the NVIDIA Cloud Accounts onboarded with Cloud Functions + */ + +export function useGetCloudAccountsSuspense< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetCloudAccountsSuspenseQueryOptions(options); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.msw.ts b/src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.msw.ts new file mode 100644 index 000000000..119f3e9aa --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.msw.ts @@ -0,0 +1,523 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ +import { faker } from "@faker-js/faker/locale/en"; +import type { RequestHandlerOptions } from "msw"; +import { delay, HttpResponse, http } from "msw"; + +import type { GetClusterResponse } from "../../model/getClusterResponse"; + +export const getGetClustersResponseMock = (): GetClusterResponse[] => + Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => ({ + clusterName: (() => { + const user = faker.internet.username().toLowerCase(); + return `${user}-cluster`; + })(), + clusterGroupName: (() => { + const user = faker.internet.username().toLowerCase(); + return `${user}-cluster`; + })(), + clusterDescription: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + ncaId: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + authorizedNCAIds: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + cloudProvider: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "ON-PREM", + "DGX-CLOUD", + "AWS", + "OCI", + "AZURE", + "GDN", + "GFN", + "GCP", + "NCP", + ] as const), + undefined, + ]), + region: (() => + faker.helpers.arrayElement([ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-central-1", + "ap-south-1", + "ap-northeast-1", + ]))(), + capabilities: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + attributes: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + customAttributes: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + gpus: (() => { + const gpuDefs = [ + { name: "A100", mem: "80GB", gpuMemory: "80Gi" }, + { name: "A10G", mem: "24GB", gpuMemory: "24Gi" }, + { name: "H100", mem: "80GB", gpuMemory: "80Gi" }, + { name: "L40S", mem: "48GB", gpuMemory: "48Gi" }, + { name: "H200", mem: "141GB", gpuMemory: "141Gi" }, + ]; + + return faker.helpers + .arrayElements(gpuDefs, faker.number.int({ min: 1, max: 3 })) + .map((g) => ({ + name: g.name, + capacity: faker.helpers.arrayElement([8, 16, 32, 64]), + instanceTypes: faker.helpers + .arrayElements( + ["1x", "2x", "4x", "8x"], + faker.number.int({ min: 1, max: 3 }), + ) + .map((count) => ({ + name: `${g.name}.${g.mem}.${count}`, + value: `${g.name}.${g.mem}.${count}`, + gpuCount: Number(count.replace("x", "")), + gpuMemory: g.gpuMemory, + systemMemory: faker.helpers.arrayElement([ + "256Gi", + "512Gi", + "1024Gi", + ]), + cpuCores: faker.helpers.arrayElement([16, 32, 64, 128]), + default: false, + })), + })); + })(), + nvcaVersion: (() => `3.0.${faker.number.int({ min: 0, max: 5 })}`)(), + clusterId: (() => faker.string.uuid())(), + clusterGroupId: (() => faker.string.uuid())(), + status: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "READY", + "PAUSED", + "FAILED", + "ABANDONED", + "NOT_READY", + "UNHEALTHY", + "DELETED", + "CORDON", + "CORDON_AND_DRAIN", + ] as const), + undefined, + ]), + clusterSource: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + nvcaLastConnected: faker.helpers.arrayElement([ + faker.date.past().toISOString().slice(0, 19) + "Z", + undefined, + ]), + k8sVersion: (() => + `v1.${faker.number.int({ min: 29, max: 34 })}.${faker.number.int({ min: 0, max: 9 })}`)(), + gpuUsage: (() => { + const gpuName = faker.helpers.arrayElement([ + "H100", + "A100", + "L40S", + "H200", + ]); + const capacity = faker.helpers.arrayElement([8, 16, 32, 64]); + const allocated = faker.number.int({ + min: 0, + max: capacity, + }); + return { + [gpuName]: { + capacity, + allocated, + available: capacity - allocated, + }, + }; + })(), + sisConfig: faker.helpers.arrayElement([ + { + publicKeysetEndpoint: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + tokenURL: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + spotServiceURL: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + }, + undefined, + ]), + vaultConfig: faker.helpers.arrayElement([ + { + address: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + }, + undefined, + ]), + imageCredentialHelper: faker.helpers.arrayElement([ + { + imageConfig: faker.helpers.arrayElement([ + { + repository: faker.string.alpha({ length: { min: 1, max: 20 } }), + tag: faker.string.alpha({ length: { min: 1, max: 20 } }), + }, + undefined, + ]), + }, + undefined, + ]), + clusterUpgradeStatus: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "IN_PROGRESS", + "AVAILABLE", + "PAUSED", + "SUCCESS", + "FAILED", + ] as const), + undefined, + ]), + clusterKeyId: (() => faker.string.uuid())(), + clusterConfigurations: faker.helpers.arrayElement([ + { + [faker.string.alphanumeric(5)]: faker.string.alpha({ + length: { min: 10, max: 20 }, + }), + }, + undefined, + ]), + clusterConfigurationFiles: faker.helpers.arrayElement([ + { + [faker.string.alphanumeric(5)]: faker.string.alpha({ + length: { min: 10, max: 20 }, + }), + }, + undefined, + ]), + })); + +export const getGetClusterForNcaIdAndClusterIdResponseMock = ( + overrideResponse: Partial> = {}, +): GetClusterResponse => ({ + clusterName: (() => { + const user = faker.internet.username().toLowerCase(); + return `${user}-cluster`; + })(), + clusterGroupName: (() => { + const user = faker.internet.username().toLowerCase(); + return `${user}-cluster`; + })(), + clusterDescription: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + ncaId: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + authorizedNCAIds: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + cloudProvider: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "ON-PREM", + "DGX-CLOUD", + "AWS", + "OCI", + "AZURE", + "GDN", + "GFN", + "GCP", + "NCP", + ] as const), + undefined, + ]), + region: (() => + faker.helpers.arrayElement([ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-central-1", + "ap-south-1", + "ap-northeast-1", + ]))(), + capabilities: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + attributes: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + customAttributes: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 1, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + gpus: (() => { + const gpuDefs = [ + { name: "A100", mem: "80GB", gpuMemory: "80Gi" }, + { name: "A10G", mem: "24GB", gpuMemory: "24Gi" }, + { name: "H100", mem: "80GB", gpuMemory: "80Gi" }, + { name: "L40S", mem: "48GB", gpuMemory: "48Gi" }, + { name: "H200", mem: "141GB", gpuMemory: "141Gi" }, + ]; + + return faker.helpers + .arrayElements(gpuDefs, faker.number.int({ min: 1, max: 3 })) + .map((g) => ({ + name: g.name, + capacity: faker.helpers.arrayElement([8, 16, 32, 64]), + instanceTypes: faker.helpers + .arrayElements( + ["1x", "2x", "4x", "8x"], + faker.number.int({ min: 1, max: 3 }), + ) + .map((count) => ({ + name: `${g.name}.${g.mem}.${count}`, + value: `${g.name}.${g.mem}.${count}`, + gpuCount: Number(count.replace("x", "")), + gpuMemory: g.gpuMemory, + systemMemory: faker.helpers.arrayElement([ + "256Gi", + "512Gi", + "1024Gi", + ]), + cpuCores: faker.helpers.arrayElement([16, 32, 64, 128]), + default: false, + })), + })); + })(), + nvcaVersion: (() => `3.0.${faker.number.int({ min: 0, max: 5 })}`)(), + clusterId: (() => faker.string.uuid())(), + clusterGroupId: (() => faker.string.uuid())(), + status: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "READY", + "PAUSED", + "FAILED", + "ABANDONED", + "NOT_READY", + "UNHEALTHY", + "DELETED", + "CORDON", + "CORDON_AND_DRAIN", + ] as const), + undefined, + ]), + clusterSource: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + nvcaLastConnected: faker.helpers.arrayElement([ + faker.date.past().toISOString().slice(0, 19) + "Z", + undefined, + ]), + k8sVersion: (() => + `v1.${faker.number.int({ min: 29, max: 34 })}.${faker.number.int({ min: 0, max: 9 })}`)(), + gpuUsage: (() => { + const gpuName = faker.helpers.arrayElement([ + "H100", + "A100", + "L40S", + "H200", + ]); + const capacity = faker.helpers.arrayElement([8, 16, 32, 64]); + const allocated = faker.number.int({ + min: 0, + max: capacity, + }); + return { + [gpuName]: { + capacity, + allocated, + available: capacity - allocated, + }, + }; + })(), + sisConfig: faker.helpers.arrayElement([ + { + publicKeysetEndpoint: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + tokenURL: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + spotServiceURL: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + }, + undefined, + ]), + vaultConfig: faker.helpers.arrayElement([ + { + address: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + }, + undefined, + ]), + imageCredentialHelper: faker.helpers.arrayElement([ + { + imageConfig: faker.helpers.arrayElement([ + { + repository: faker.string.alpha({ length: { min: 1, max: 20 } }), + tag: faker.string.alpha({ length: { min: 1, max: 20 } }), + }, + undefined, + ]), + }, + undefined, + ]), + clusterUpgradeStatus: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "IN_PROGRESS", + "AVAILABLE", + "PAUSED", + "SUCCESS", + "FAILED", + ] as const), + undefined, + ]), + clusterKeyId: (() => faker.string.uuid())(), + clusterConfigurations: faker.helpers.arrayElement([ + { + [faker.string.alphanumeric(5)]: faker.string.alpha({ + length: { min: 10, max: 20 }, + }), + }, + undefined, + ]), + clusterConfigurationFiles: faker.helpers.arrayElement([ + { + [faker.string.alphanumeric(5)]: faker.string.alpha({ + length: { min: 10, max: 20 }, + }), + }, + undefined, + ]), + ...overrideResponse, +}); + +export const getGetClustersMockHandler = ( + overrideResponse?: + | GetClusterResponse[] + | (( + info: Parameters[1]>[0], + ) => Promise | GetClusterResponse[]), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v1/si/accounts/:ncaId/clusters", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetClustersResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; + +export const getGetClusterForNcaIdAndClusterIdMockHandler = ( + overrideResponse?: + | GetClusterResponse + | (( + info: Parameters[1]>[0], + ) => Promise | GetClusterResponse), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v1/si/accounts/:ncaId/clusters/:clusterId", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetClusterForNcaIdAndClusterIdResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; +export const getClustersMock = () => [ + getGetClustersMockHandler(), + getGetClusterForNcaIdAndClusterIdMockHandler(), +]; diff --git a/src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.ts b/src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.ts new file mode 100644 index 000000000..72c57a53b --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/clusters/clusters.ts @@ -0,0 +1,695 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, + UseSuspenseQueryOptions, + UseSuspenseQueryResult, +} from "@tanstack/react-query"; +import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; +import { customFetch } from "../../../lib/fetch"; +import type { GetClusterResponse } from "../../model/getClusterResponse"; +import type { GetClustersParams } from "../../model/getClustersParams"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * API to get the cluster details for given NcaId + * @summary Get NVCA Clusters + */ +export const getGetClustersUrl = ( + ncaId: string, + params?: GetClustersParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/v1/si/accounts/${ncaId}/clusters?${stringifiedParams}` + : `/v1/si/accounts/${ncaId}/clusters`; +}; + +export const getClusters = async ( + ncaId: string, + params?: GetClustersParams, + options?: RequestInit, +): Promise => { + return customFetch(getGetClustersUrl(ncaId, params), { + ...options, + method: "GET", + }); +}; + +export const getGetClustersQueryKey = ( + ncaId: string, + params?: GetClustersParams, +) => { + return [ + `/v1/si/accounts/${ncaId}/clusters`, + ...(params ? [params] : []), + ] as const; +}; + +export const getGetClustersQueryOptions = < + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getGetClustersQueryKey(ncaId, params); + + const queryFn: QueryFunction>> = ({ + signal, + }) => getClusters(ncaId, params, { signal, ...requestOptions }); + + return { + queryKey, + queryFn, + enabled: !!ncaId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetClustersQueryResult = NonNullable< + Awaited> +>; +export type GetClustersQueryError = void; + +export function useGetClusters< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params: undefined | GetClustersParams, + options: { + query: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetClusters< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetClusters< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get NVCA Clusters + */ + +export function useGetClusters< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetClustersQueryOptions(ncaId, params, options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetClustersSuspenseQueryOptions = < + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getGetClustersQueryKey(ncaId, params); + + const queryFn: QueryFunction>> = ({ + signal, + }) => getClusters(ncaId, params, { signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetClustersSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetClustersSuspenseQueryError = void; + +export function useGetClustersSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params: undefined | GetClustersParams, + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetClustersSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetClustersSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get NVCA Clusters + */ + +export function useGetClustersSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + params?: GetClustersParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetClustersSuspenseQueryOptions( + ncaId, + params, + options, + ); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * API to get the cluster details for ncaId and clusterId + * @summary Get NVCA Cluster + */ +export const getGetClusterForNcaIdAndClusterIdUrl = ( + ncaId: string, + clusterId: string, +) => { + return `/v1/si/accounts/${ncaId}/clusters/${clusterId}`; +}; + +export const getClusterForNcaIdAndClusterId = async ( + ncaId: string, + clusterId: string, + options?: RequestInit, +): Promise => { + return customFetch( + getGetClusterForNcaIdAndClusterIdUrl(ncaId, clusterId), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetClusterForNcaIdAndClusterIdQueryKey = ( + ncaId: string, + clusterId: string, +) => { + return [`/v1/si/accounts/${ncaId}/clusters/${clusterId}`] as const; +}; + +export const getGetClusterForNcaIdAndClusterIdQueryOptions = < + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetClusterForNcaIdAndClusterIdQueryKey(ncaId, clusterId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getClusterForNcaIdAndClusterId(ncaId, clusterId, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!(ncaId && clusterId), + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetClusterForNcaIdAndClusterIdQueryResult = NonNullable< + Awaited> +>; +export type GetClusterForNcaIdAndClusterIdQueryError = void; + +export function useGetClusterForNcaIdAndClusterId< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetClusterForNcaIdAndClusterId< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetClusterForNcaIdAndClusterId< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get NVCA Cluster + */ + +export function useGetClusterForNcaIdAndClusterId< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetClusterForNcaIdAndClusterIdQueryOptions( + ncaId, + clusterId, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetClusterForNcaIdAndClusterIdSuspenseQueryOptions = < + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetClusterForNcaIdAndClusterIdQueryKey(ncaId, clusterId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getClusterForNcaIdAndClusterId(ncaId, clusterId, { + signal, + ...requestOptions, + }); + + return { queryKey, queryFn, ...queryOptions } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetClusterForNcaIdAndClusterIdSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetClusterForNcaIdAndClusterIdSuspenseQueryError = void; + +export function useGetClusterForNcaIdAndClusterIdSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetClusterForNcaIdAndClusterIdSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetClusterForNcaIdAndClusterIdSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get NVCA Cluster + */ + +export function useGetClusterForNcaIdAndClusterIdSuspense< + TData = Awaited>, + TError = void, +>( + ncaId: string, + clusterId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetClusterForNcaIdAndClusterIdSuspenseQueryOptions( + ncaId, + clusterId, + options, + ); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.msw.ts b/src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.msw.ts new file mode 100644 index 000000000..9e4a743e2 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.msw.ts @@ -0,0 +1,76 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * NVCF UI Shim API + * API for the NVCF UI shim service + * OpenAPI spec version: 0.1.0 + */ + +import type { RequestHandlerOptions } from "msw"; +import { delay, HttpResponse, http } from "msw"; + +import type { ControlPlaneComponentStatus } from "../../model/controlPlaneComponentStatus"; + +export const getGetControlPlaneStatusResponseMock = + (): ControlPlaneComponentStatus[] => [ + { + componentName: "sis-api", + namespace: "sis", + status: "healthy", + timestamp: "2026-06-27T10:00:00Z", + }, + { + componentName: "ess-api", + namespace: "ess", + status: "unhealthy", + timestamp: "2026-06-27T10:00:00Z", + }, + ]; + +export const getGetControlPlaneStatusMockHandler = ( + overrideResponse?: + | ControlPlaneComponentStatus[] + | (( + info: Parameters[1]>[0], + ) => + | Promise + | ControlPlaneComponentStatus[]), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v1/control-plane", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetControlPlaneStatusResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; +export const getControlPlaneMock = () => [ + getGetControlPlaneStatusMockHandler(), +]; diff --git a/src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.ts b/src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.ts new file mode 100644 index 000000000..f39973db8 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/control-plane/control-plane.ts @@ -0,0 +1,322 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * NVCF UI Shim API + * API for the NVCF UI shim service + * OpenAPI spec version: 0.1.0 + */ + +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, + UseSuspenseQueryOptions, + UseSuspenseQueryResult, +} from "@tanstack/react-query"; +import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; +import { customFetch } from "../../../lib/fetch"; +import type { ControlPlaneComponentStatus } from "../../model/controlPlaneComponentStatus"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * @summary Get control plane component health statuses + */ +export const getGetControlPlaneStatusUrl = () => { + return `/v1/control-plane`; +}; + +export const getControlPlaneStatus = async ( + options?: RequestInit, +): Promise => { + return customFetch( + getGetControlPlaneStatusUrl(), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetControlPlaneStatusQueryKey = () => { + return [`/v1/control-plane`] as const; +}; + +export const getGetControlPlaneStatusQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetControlPlaneStatusQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => getControlPlaneStatus({ signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetControlPlaneStatusQueryResult = NonNullable< + Awaited> +>; +export type GetControlPlaneStatusQueryError = unknown; + +export function useGetControlPlaneStatus< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetControlPlaneStatus< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetControlPlaneStatus< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get control plane component health statuses + */ + +export function useGetControlPlaneStatus< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetControlPlaneStatusQueryOptions(options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetControlPlaneStatusSuspenseQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetControlPlaneStatusQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => getControlPlaneStatus({ signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetControlPlaneStatusSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetControlPlaneStatusSuspenseQueryError = unknown; + +export function useGetControlPlaneStatusSuspense< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetControlPlaneStatusSuspense< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetControlPlaneStatusSuspense< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get control plane component health statuses + */ + +export function useGetControlPlaneStatusSuspense< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetControlPlaneStatusSuspenseQueryOptions(options); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.msw.ts b/src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.msw.ts new file mode 100644 index 000000000..ca143991d --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.msw.ts @@ -0,0 +1,243 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import { faker } from "@faker-js/faker/locale/en"; +import type { RequestHandlerOptions } from "msw"; +import { delay, HttpResponse, http } from "msw"; + +import type { DeploymentResponse } from "../../model/deploymentResponse"; + +import type { ListDeploymentsResponse } from "../../model/listDeploymentsResponse"; + +export const getGetAllFunctionDeploymentsResponseMock = ( + overrideResponse: Partial> = {}, +): ListDeploymentsResponse => ({ + deployments: Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => ({ + functionId: faker.string.uuid(), + functionVersionId: faker.string.uuid(), + deploymentId: faker.string.uuid(), + functionName: faker.string.alpha({ length: { min: 1, max: 20 } }), + ncaId: faker.string.alpha({ length: { min: 1, max: 20 } }), + functionStatus: faker.helpers.arrayElement([ + "ACTIVE", + "DEPLOYING", + "ERROR", + "INACTIVE", + "DELETED", + "DEGRADED", + "DEGRADING", + ] as const), + healthInfo: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => ({ + icmsRequestId: faker.string.uuid(), + gpu: faker.string.alpha({ length: { min: 1, max: 20 } }), + backend: (() => faker.helpers.arrayElement(["AWS", "GCP", "AZURE"]))(), + instanceType: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + error: faker.string.alpha({ length: { min: 1, max: 20 } }), + })), + undefined, + ]), + deploymentSpecifications: (() => { + const gpus = [ + { gpu: "A100", mem: "80GB", gpuMemory: "80Gi" }, + { gpu: "A10G", mem: "24GB", gpuMemory: "24Gi" }, + { gpu: "H100", mem: "80GB", gpuMemory: "80Gi" }, + { gpu: "L40S", mem: "48GB", gpuMemory: "48Gi" }, + ]; + + const combos = gpus.flatMap((g) => + ["1x", "2x", "4x", "8x"].map((count) => ({ ...g, count })), + ); + return faker.helpers + .arrayElements(combos, faker.number.int({ min: 1, max: 5 })) + .map((c) => ({ + gpu: c.gpu, + instanceType: `${c.gpu}.${c.mem}.${c.count}`, + gpuMemory: c.gpuMemory, + systemMemory: faker.helpers.arrayElement([ + "256Gi", + "512Gi", + "1024Gi", + ]), + storage: faker.helpers.arrayElement(["512Gi", "1Ti", "2Ti"]), + minInstances: faker.number.int({ min: 0, max: 2 }), + maxInstances: faker.number.int({ min: 3, max: 16 }), + maxRequestConcurrency: faker.helpers.arrayElement([1, 4, 8, 16]), + regions: faker.helpers.arrayElements( + ["us-east-1", "us-west-2", "eu-central-1", "ap-southeast-1"], + + { min: 1, max: 2 }, + ), + clusters: faker.helpers.arrayElements( + ["prod-cluster-a", "prod-cluster-b", "prod-cluster-c"], + { min: 1, max: 2 }, + ), + })); + })(), + createdAt: faker.date.past().toISOString().slice(0, 19) + "Z", + lastUpdatedAt: faker.date.past().toISOString().slice(0, 19) + "Z", + })), + ...overrideResponse, +}); + +export const getGetFunctionDeploymentResponseMock = ( + overrideResponse: Partial> = {}, +): DeploymentResponse => ({ + deployment: { + functionId: faker.string.uuid(), + functionVersionId: faker.string.uuid(), + deploymentId: faker.string.uuid(), + functionName: faker.string.alpha({ length: { min: 1, max: 20 } }), + ncaId: faker.string.alpha({ length: { min: 1, max: 20 } }), + functionStatus: faker.helpers.arrayElement([ + "ACTIVE", + "DEPLOYING", + "ERROR", + "INACTIVE", + "DELETED", + "DEGRADED", + "DEGRADING", + ] as const), + healthInfo: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => ({ + icmsRequestId: faker.string.uuid(), + gpu: faker.string.alpha({ length: { min: 1, max: 20 } }), + backend: (() => faker.helpers.arrayElement(["AWS", "GCP", "AZURE"]))(), + instanceType: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + error: faker.string.alpha({ length: { min: 1, max: 20 } }), + })), + undefined, + ]), + deploymentSpecifications: (() => { + const gpus = [ + { gpu: "A100", mem: "80GB", gpuMemory: "80Gi" }, + { gpu: "A10G", mem: "24GB", gpuMemory: "24Gi" }, + { gpu: "H100", mem: "80GB", gpuMemory: "80Gi" }, + { gpu: "L40S", mem: "48GB", gpuMemory: "48Gi" }, + ]; + + const combos = gpus.flatMap((g) => + ["1x", "2x", "4x", "8x"].map((count) => ({ ...g, count })), + ); + return faker.helpers + .arrayElements(combos, faker.number.int({ min: 1, max: 5 })) + .map((c) => ({ + gpu: c.gpu, + instanceType: `${c.gpu}.${c.mem}.${c.count}`, + gpuMemory: c.gpuMemory, + systemMemory: faker.helpers.arrayElement([ + "256Gi", + "512Gi", + "1024Gi", + ]), + storage: faker.helpers.arrayElement(["512Gi", "1Ti", "2Ti"]), + minInstances: faker.number.int({ min: 0, max: 2 }), + maxInstances: faker.number.int({ min: 3, max: 16 }), + maxRequestConcurrency: faker.helpers.arrayElement([1, 4, 8, 16]), + regions: faker.helpers.arrayElements( + ["us-east-1", "us-west-2", "eu-central-1", "ap-southeast-1"], + + { min: 1, max: 2 }, + ), + clusters: faker.helpers.arrayElements( + ["prod-cluster-a", "prod-cluster-b", "prod-cluster-c"], + { min: 1, max: 2 }, + ), + })); + })(), + createdAt: faker.date.past().toISOString().slice(0, 19) + "Z", + lastUpdatedAt: faker.date.past().toISOString().slice(0, 19) + "Z", + }, + ...overrideResponse, +}); + +export const getGetAllFunctionDeploymentsMockHandler = ( + overrideResponse?: + | ListDeploymentsResponse + | (( + info: Parameters[1]>[0], + ) => Promise | ListDeploymentsResponse), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v2/nvcf/accounts/:ncaId/deployments", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetAllFunctionDeploymentsResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; + +export const getGetFunctionDeploymentMockHandler = ( + overrideResponse?: + | DeploymentResponse + | (( + info: Parameters[1]>[0], + ) => Promise | DeploymentResponse), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v2/nvcf/accounts/:ncaId/deployments/functions/:functionId/versions/:functionVersionId", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetFunctionDeploymentResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; +export const getFunctionDeploymentMock = () => [ + getGetAllFunctionDeploymentsMockHandler(), + getGetFunctionDeploymentMockHandler(), +]; diff --git a/src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.ts b/src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.ts new file mode 100644 index 000000000..6909da99e --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/function-deployment/function-deployment.ts @@ -0,0 +1,704 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, + UseSuspenseQueryOptions, + UseSuspenseQueryResult, +} from "@tanstack/react-query"; +import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; +import { customFetch } from "../../../lib/fetch"; +import type { DeploymentResponse } from "../../model/deploymentResponse"; +import type { ListDeploymentsResponse } from "../../model/listDeploymentsResponse"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * Retrieves deployments details of all the functions in the specified account + * @summary Get All Deployments + */ +export const getGetAllFunctionDeploymentsUrl = (ncaId: string) => { + return `/v2/nvcf/accounts/${ncaId}/deployments`; +}; + +export const getAllFunctionDeployments = async ( + ncaId: string, + options?: RequestInit, +): Promise => { + return customFetch( + getGetAllFunctionDeploymentsUrl(ncaId), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetAllFunctionDeploymentsQueryKey = (ncaId: string) => { + return [`/v2/nvcf/accounts/${ncaId}/deployments`] as const; +}; + +export const getGetAllFunctionDeploymentsQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getGetAllFunctionDeploymentsQueryKey(ncaId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getAllFunctionDeployments(ncaId, { signal, ...requestOptions }); + + return { + queryKey, + queryFn, + enabled: !!ncaId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetAllFunctionDeploymentsQueryResult = NonNullable< + Awaited> +>; +export type GetAllFunctionDeploymentsQueryError = unknown; + +export function useGetAllFunctionDeployments< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctionDeployments< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctionDeployments< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get All Deployments + */ + +export function useGetAllFunctionDeployments< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetAllFunctionDeploymentsQueryOptions(ncaId, options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetAllFunctionDeploymentsSuspenseQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getGetAllFunctionDeploymentsQueryKey(ncaId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getAllFunctionDeployments(ncaId, { signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetAllFunctionDeploymentsSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetAllFunctionDeploymentsSuspenseQueryError = unknown; + +export function useGetAllFunctionDeploymentsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctionDeploymentsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctionDeploymentsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get All Deployments + */ + +export function useGetAllFunctionDeploymentsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetAllFunctionDeploymentsSuspenseQueryOptions( + ncaId, + options, + ); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * Retrieves deployment details of the specified function + * @summary Get Function Deployment Details + */ +export const getGetFunctionDeploymentUrl = ( + ncaId: string, + functionId: string, + functionVersionId: string, +) => { + return `/v2/nvcf/accounts/${ncaId}/deployments/functions/${functionId}/versions/${functionVersionId}`; +}; + +export const getFunctionDeployment = async ( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: RequestInit, +): Promise => { + return customFetch( + getGetFunctionDeploymentUrl(ncaId, functionId, functionVersionId), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetFunctionDeploymentQueryKey = ( + ncaId: string, + functionId: string, + functionVersionId: string, +) => { + return [ + `/v2/nvcf/accounts/${ncaId}/deployments/functions/${functionId}/versions/${functionVersionId}`, + ] as const; +}; + +export const getGetFunctionDeploymentQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetFunctionDeploymentQueryKey(ncaId, functionId, functionVersionId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getFunctionDeployment(ncaId, functionId, functionVersionId, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!(ncaId && functionId && functionVersionId), + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetFunctionDeploymentQueryResult = NonNullable< + Awaited> +>; +export type GetFunctionDeploymentQueryError = unknown; + +export function useGetFunctionDeployment< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionDeployment< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionDeployment< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Function Deployment Details + */ + +export function useGetFunctionDeployment< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetFunctionDeploymentQueryOptions( + ncaId, + functionId, + functionVersionId, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetFunctionDeploymentSuspenseQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetFunctionDeploymentQueryKey(ncaId, functionId, functionVersionId); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getFunctionDeployment(ncaId, functionId, functionVersionId, { + signal, + ...requestOptions, + }); + + return { queryKey, queryFn, ...queryOptions } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetFunctionDeploymentSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetFunctionDeploymentSuspenseQueryError = unknown; + +export function useGetFunctionDeploymentSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionDeploymentSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionDeploymentSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Function Deployment Details + */ + +export function useGetFunctionDeploymentSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetFunctionDeploymentSuspenseQueryOptions( + ncaId, + functionId, + functionVersionId, + options, + ); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.msw.ts b/src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.msw.ts new file mode 100644 index 000000000..d28dc28f7 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.msw.ts @@ -0,0 +1,547 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import { faker } from "@faker-js/faker/locale/en"; +import type { RequestHandlerOptions } from "msw"; +import { delay, HttpResponse, http } from "msw"; + +import type { FunctionResponse } from "../../model/functionResponse"; + +import type { ListFunctionsResponse } from "../../model/listFunctionsResponse"; + +export const getGetAllFunctionsResponseMock = ( + overrideResponse: Partial> = {}, +): ListFunctionsResponse => ({ + functions: Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => ({ + id: faker.string.uuid(), + ncaId: faker.string.alpha({ length: { min: 10, max: 20 } }), + versionId: faker.string.uuid(), + name: (() => faker.lorem.slug())(), + status: faker.helpers.arrayElement([ + "ACTIVE", + "DEPLOYING", + "ERROR", + "INACTIVE", + "DELETED", + "DEGRADED", + "DEGRADING", + ] as const), + inferenceUrl: (() => + `https://api.nvcf.nvidia.com/v2/nvcf/pexec/functions/${faker.string.uuid()}`)(), + ownedByDifferentAccount: faker.helpers.arrayElement([ + faker.datatype.boolean(), + undefined, + ]), + inferencePort: faker.helpers.arrayElement([ + faker.number.int({ min: 0, max: 100 }), + undefined, + ]), + containerArgs: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + containerEnvironment: (() => + faker.helpers.maybe( + () => + Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => ({ + key: `${faker.lorem.word().toUpperCase()}_${faker.lorem.word().toUpperCase()}`, + value: faker.string.alphanumeric(16), + })), + { probability: 0.7 }, + ))(), + models: (() => + faker.helpers.maybe( + () => + Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => ({ + name: faker.lorem.slug(), + uri: `api.ngc.nvidia.com/v2/org/nvidia/models/${faker.lorem.slug()}/versions/${faker.system.semver()}`, + })), + { probability: 0.7 }, + ))(), + containerImage: (() => + `nvcr.io/nvidia/${faker.lorem.slug()}:${faker.system.semver()}`)(), + apiBodyFormat: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["PREDICT_V2", "CUSTOM"] as const), + undefined, + ]), + helmChart: (() => `helm.ngc.nvidia.com/nvidia/${faker.lorem.slug()}`)(), + helmChartServiceName: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + healthUri: faker.internet.url(), + createdAt: faker.date.past().toISOString().slice(0, 19) + "Z", + activeInstances: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => ({ + instanceId: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + functionId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + functionVersionId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + instanceType: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + instanceStatus: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "ACTIVE", + "STARTING", + "RUNNING", + "ERRORED", + "PREEMPTED", + "DELETED", + "TERMINATED", + ] as const), + undefined, + ]), + icmsRequestId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + ncaId: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + gpu: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + backend: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + location: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + instanceCreatedAt: faker.helpers.arrayElement([ + faker.date.past().toISOString().slice(0, 19) + "Z", + undefined, + ]), + instanceUpdatedAt: faker.helpers.arrayElement([ + faker.date.past().toISOString().slice(0, 19) + "Z", + undefined, + ]), + })), + undefined, + ]), + resources: (() => + faker.helpers.maybe( + () => + Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => ({ + name: faker.lorem.slug(), + version: faker.system.semver(), + uri: `api.ngc.nvidia.com/v2/org/nvidia/resources/${faker.lorem.slug()}/versions/${faker.system.semver()}`, + })), + { probability: 0.7 }, + ))(), + tags: (() => + faker.helpers.arrayElements(["inference", "gpu", "llm"], { + min: 0, + max: 3, + }))(), + description: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + health: faker.helpers.arrayElement([ + { + protocol: faker.helpers.arrayElement(["HTTP", "gRPC"] as const), + uri: (() => + faker.helpers.arrayElement([ + "health/ready", + "healthz", + "v1/health/ready", + ]))(), + port: faker.number.int({ min: 0, max: 100 }), + timeout: faker.string.alpha({ length: { min: 10, max: 20 } }), + expectedStatusCode: faker.number.int({ min: 0, max: 100 }), + }, + undefined, + ]), + functionType: faker.helpers.arrayElement([ + "DEFAULT", + "STREAMING", + "LLM", + ] as const), + secrets: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + rateLimit: faker.helpers.arrayElement([ + { + rateLimit: faker.helpers.arrayElement([ + faker.helpers.fromRegExp( + "^(?!.*-([SMHD]).*-\\1)[1-9]\\d*-[SMHD](,\\s*[1-9]\\d*-[SMHD])*$", + ), + undefined, + ]), + exemptedNcaIds: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 32 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + perNcaIdRate: faker.helpers.arrayElement([ + { + [faker.string.alphanumeric(5)]: faker.string.alpha({ + length: { min: 10, max: 20 }, + }), + }, + undefined, + ]), + syncCheck: faker.helpers.arrayElement([ + faker.datatype.boolean(), + undefined, + ]), + perUserRate: faker.helpers.arrayElement([ + faker.helpers.fromRegExp( + "^(?!.*-([SMHD]).*-\\1)[1-9]\\d*-[SMHD](,\\s*[1-9]\\d*-[SMHD])*$", + ), + undefined, + ]), + }, + undefined, + ]), + telemetries: faker.helpers.arrayElement([ + { + logsTelemetryId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + metricsTelemetryId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + tracesTelemetryId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + }, + undefined, + ]), + })), + ...overrideResponse, +}); + +export const getGetFunctionVersionResponseMock = ( + overrideResponse: Partial> = {}, +): FunctionResponse => ({ + function: { + id: faker.string.uuid(), + ncaId: faker.string.alpha({ length: { min: 10, max: 20 } }), + versionId: faker.string.uuid(), + name: (() => faker.lorem.slug())(), + status: faker.helpers.arrayElement([ + "ACTIVE", + "DEPLOYING", + "ERROR", + "INACTIVE", + "DELETED", + "DEGRADED", + "DEGRADING", + ] as const), + inferenceUrl: (() => + `https://api.nvcf.nvidia.com/v2/nvcf/pexec/functions/${faker.string.uuid()}`)(), + ownedByDifferentAccount: faker.helpers.arrayElement([ + faker.datatype.boolean(), + undefined, + ]), + inferencePort: faker.helpers.arrayElement([ + faker.number.int({ min: 0, max: 100 }), + undefined, + ]), + containerArgs: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + containerEnvironment: (() => + faker.helpers.maybe( + () => + Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => ({ + key: `${faker.lorem.word().toUpperCase()}_${faker.lorem.word().toUpperCase()}`, + value: faker.string.alphanumeric(16), + })), + { probability: 0.7 }, + ))(), + models: (() => + faker.helpers.maybe( + () => + Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => ({ + name: faker.lorem.slug(), + uri: `api.ngc.nvidia.com/v2/org/nvidia/models/${faker.lorem.slug()}/versions/${faker.system.semver()}`, + })), + { probability: 0.7 }, + ))(), + containerImage: (() => + `nvcr.io/nvidia/${faker.lorem.slug()}:${faker.system.semver()}`)(), + apiBodyFormat: faker.helpers.arrayElement([ + faker.helpers.arrayElement(["PREDICT_V2", "CUSTOM"] as const), + undefined, + ]), + helmChart: (() => `helm.ngc.nvidia.com/nvidia/${faker.lorem.slug()}`)(), + helmChartServiceName: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + healthUri: faker.internet.url(), + createdAt: faker.date.past().toISOString().slice(0, 19) + "Z", + activeInstances: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => ({ + instanceId: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + functionId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + functionVersionId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + instanceType: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + instanceStatus: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "ACTIVE", + "STARTING", + "RUNNING", + "ERRORED", + "PREEMPTED", + "DELETED", + "TERMINATED", + ] as const), + undefined, + ]), + icmsRequestId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + ncaId: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + gpu: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + backend: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + location: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + instanceCreatedAt: faker.helpers.arrayElement([ + faker.date.past().toISOString().slice(0, 19) + "Z", + undefined, + ]), + instanceUpdatedAt: faker.helpers.arrayElement([ + faker.date.past().toISOString().slice(0, 19) + "Z", + undefined, + ]), + })), + undefined, + ]), + resources: (() => + faker.helpers.maybe( + () => + Array.from({ length: faker.number.int({ min: 1, max: 3 }) }, () => ({ + name: faker.lorem.slug(), + version: faker.system.semver(), + uri: `api.ngc.nvidia.com/v2/org/nvidia/resources/${faker.lorem.slug()}/versions/${faker.system.semver()}`, + })), + { probability: 0.7 }, + ))(), + tags: (() => + faker.helpers.arrayElements(["inference", "gpu", "llm"], { + min: 0, + max: 3, + }))(), + description: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + health: faker.helpers.arrayElement([ + { + protocol: faker.helpers.arrayElement(["HTTP", "gRPC"] as const), + uri: (() => + faker.helpers.arrayElement([ + "health/ready", + "healthz", + "v1/health/ready", + ]))(), + port: faker.number.int({ min: 0, max: 100 }), + timeout: faker.string.alpha({ length: { min: 10, max: 20 } }), + expectedStatusCode: faker.number.int({ min: 0, max: 100 }), + }, + undefined, + ]), + functionType: faker.helpers.arrayElement([ + "DEFAULT", + "STREAMING", + "LLM", + ] as const), + secrets: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 25 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + rateLimit: faker.helpers.arrayElement([ + { + rateLimit: faker.helpers.arrayElement([ + faker.helpers.fromRegExp( + "^(?!.*-([SMHD]).*-\\1)[1-9]\\d*-[SMHD](,\\s*[1-9]\\d*-[SMHD])*$", + ), + undefined, + ]), + exemptedNcaIds: faker.helpers.arrayElement([ + Array.from( + { length: faker.number.int({ min: 0, max: 32 }) }, + (_, i) => i + 1, + ).map(() => faker.string.alpha({ length: { min: 10, max: 20 } })), + undefined, + ]), + perNcaIdRate: faker.helpers.arrayElement([ + { + [faker.string.alphanumeric(5)]: faker.string.alpha({ + length: { min: 10, max: 20 }, + }), + }, + undefined, + ]), + syncCheck: faker.helpers.arrayElement([ + faker.datatype.boolean(), + undefined, + ]), + perUserRate: faker.helpers.arrayElement([ + faker.helpers.fromRegExp( + "^(?!.*-([SMHD]).*-\\1)[1-9]\\d*-[SMHD](,\\s*[1-9]\\d*-[SMHD])*$", + ), + undefined, + ]), + }, + undefined, + ]), + telemetries: faker.helpers.arrayElement([ + { + logsTelemetryId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + metricsTelemetryId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + tracesTelemetryId: faker.helpers.arrayElement([ + faker.string.uuid(), + undefined, + ]), + }, + undefined, + ]), + }, + ...overrideResponse, +}); + +export const getGetAllFunctionsMockHandler = ( + overrideResponse?: + | ListFunctionsResponse + | (( + info: Parameters[1]>[0], + ) => Promise | ListFunctionsResponse), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v2/nvcf/accounts/:ncaId/functions", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetAllFunctionsResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; + +export const getGetFunctionVersionMockHandler = ( + overrideResponse?: + | FunctionResponse + | (( + info: Parameters[1]>[0], + ) => Promise | FunctionResponse), + options?: RequestHandlerOptions, +) => { + return http.get( + "/v2/nvcf/accounts/:ncaId/functions/:functionId/versions/:functionVersionId", + async (info: Parameters[1]>[0]) => { + await delay(150); + + return HttpResponse.json( + overrideResponse !== undefined + ? typeof overrideResponse === "function" + ? await overrideResponse(info) + : overrideResponse + : getGetFunctionVersionResponseMock(), + { status: 200 }, + ); + }, + options, + ); +}; +export const getFunctionManagementMock = () => [ + getGetAllFunctionsMockHandler(), + getGetFunctionVersionMockHandler(), +]; diff --git a/src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.ts b/src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.ts new file mode 100644 index 000000000..a9da77129 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/api/function-management/function-management.ts @@ -0,0 +1,765 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, + UseSuspenseQueryOptions, + UseSuspenseQueryResult, +} from "@tanstack/react-query"; +import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; +import { customFetch } from "../../../lib/fetch"; +import type { FunctionResponse } from "../../model/functionResponse"; +import type { GetAllFunctionsParams } from "../../model/getAllFunctionsParams"; +import type { GetFunctionVersionParams } from "../../model/getFunctionVersionParams"; +import type { ListFunctionsResponse } from "../../model/listFunctionsResponse"; + +type SecondParameter unknown> = Parameters[1]; + +/** + * Lists all the functions associated with the specified NVIDIA Cloud Account. + * @summary List Functions + */ +export const getGetAllFunctionsUrl = ( + ncaId: string, + params?: GetAllFunctionsParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/v2/nvcf/accounts/${ncaId}/functions?${stringifiedParams}` + : `/v2/nvcf/accounts/${ncaId}/functions`; +}; + +export const getAllFunctions = async ( + ncaId: string, + params?: GetAllFunctionsParams, + options?: RequestInit, +): Promise => { + return customFetch( + getGetAllFunctionsUrl(ncaId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetAllFunctionsQueryKey = ( + ncaId: string, + params?: GetAllFunctionsParams, +) => { + return [ + `/v2/nvcf/accounts/${ncaId}/functions`, + ...(params ? [params] : []), + ] as const; +}; + +export const getGetAllFunctionsQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getGetAllFunctionsQueryKey(ncaId, params); + + const queryFn: QueryFunction>> = ({ + signal, + }) => getAllFunctions(ncaId, params, { signal, ...requestOptions }); + + return { + queryKey, + queryFn, + enabled: !!ncaId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetAllFunctionsQueryResult = NonNullable< + Awaited> +>; +export type GetAllFunctionsQueryError = unknown; + +export function useGetAllFunctions< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params: undefined | GetAllFunctionsParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctions< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctions< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Functions + */ + +export function useGetAllFunctions< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetAllFunctionsQueryOptions(ncaId, params, options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetAllFunctionsSuspenseQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getGetAllFunctionsQueryKey(ncaId, params); + + const queryFn: QueryFunction>> = ({ + signal, + }) => getAllFunctions(ncaId, params, { signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetAllFunctionsSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetAllFunctionsSuspenseQueryError = unknown; + +export function useGetAllFunctionsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params: undefined | GetAllFunctionsParams, + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctionsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetAllFunctionsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Functions + */ + +export function useGetAllFunctionsSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + params?: GetAllFunctionsParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetAllFunctionsSuspenseQueryOptions( + ncaId, + params, + options, + ); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * Retrieves detailed information of the specified function version in the specified NVIDIA Cloud Account + * @summary Get Function Version Details + */ +export const getGetFunctionVersionUrl = ( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? "null" : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/v2/nvcf/accounts/${ncaId}/functions/${functionId}/versions/${functionVersionId}?${stringifiedParams}` + : `/v2/nvcf/accounts/${ncaId}/functions/${functionId}/versions/${functionVersionId}`; +}; + +export const getFunctionVersion = async ( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: RequestInit, +): Promise => { + return customFetch( + getGetFunctionVersionUrl(ncaId, functionId, functionVersionId, params), + { + ...options, + method: "GET", + }, + ); +}; + +export const getGetFunctionVersionQueryKey = ( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, +) => { + return [ + `/v2/nvcf/accounts/${ncaId}/functions/${functionId}/versions/${functionVersionId}`, + ...(params ? [params] : []), + ] as const; +}; + +export const getGetFunctionVersionQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetFunctionVersionQueryKey(ncaId, functionId, functionVersionId, params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getFunctionVersion(ncaId, functionId, functionVersionId, params, { + signal, + ...requestOptions, + }); + + return { + queryKey, + queryFn, + enabled: !!(ncaId && functionId && functionVersionId), + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetFunctionVersionQueryResult = NonNullable< + Awaited> +>; +export type GetFunctionVersionQueryError = unknown; + +export function useGetFunctionVersion< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params: undefined | GetFunctionVersionParams, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionVersion< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionVersion< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Function Version Details + */ + +export function useGetFunctionVersion< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetFunctionVersionQueryOptions( + ncaId, + functionId, + functionVersionId, + params, + options, + ); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +export const getGetFunctionVersionSuspenseQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, +) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? + getGetFunctionVersionQueryKey(ncaId, functionId, functionVersionId, params); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + getFunctionVersion(ncaId, functionId, functionVersionId, params, { + signal, + ...requestOptions, + }); + + return { queryKey, queryFn, ...queryOptions } as UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetFunctionVersionSuspenseQueryResult = NonNullable< + Awaited> +>; +export type GetFunctionVersionSuspenseQueryError = unknown; + +export function useGetFunctionVersionSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params: undefined | GetFunctionVersionParams, + options: { + query: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionVersionSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +export function useGetFunctionVersionSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary Get Function Version Details + */ + +export function useGetFunctionVersionSuspense< + TData = Awaited>, + TError = unknown, +>( + ncaId: string, + functionId: string, + functionVersionId: string, + params?: GetFunctionVersionParams, + options?: { + query?: Partial< + UseSuspenseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseSuspenseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetFunctionVersionSuspenseQueryOptions( + ncaId, + functionId, + functionVersionId, + params, + options, + ); + + const query = useSuspenseQuery( + queryOptions, + queryClient, + ) as UseSuspenseQueryResult & { + queryKey: DataTag; + }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/accountDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/accountDto.ts new file mode 100644 index 000000000..05097e7e2 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/accountDto.ts @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Data Transfer Object(DTO) representing an account + */ +export interface AccountDto { + /** NVIDIA Cloud Account id */ + ncaId: string; + /** + * Client Ids associated with the NVIDIA Cloud Account + * @minItems 0 + */ + adminClientIds?: string[]; + /** Account/customer name */ + name: string; + /** Maximum number of functions allowed for Account */ + maxFunctionsAllowed: number; + /** Maximum number of tasks allowed for Account */ + maxTasksAllowed: number; + /** Maximum number of telemetries allowed for Account */ + maxTelemetriesAllowed: number; + /** Maximum number of registry credentials allowed for Account */ + maxRegistryCredentialsAllowed: number; + /** Last time the account was updated. */ + lastUpdatedAt?: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/artifactDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/artifactDto.ts new file mode 100644 index 000000000..e67ced377 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/artifactDto.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Data Transfer Object(DTO) representing an artifact + */ +export interface ArtifactDto { + /** Artifact name */ + name: string; + /** Artifact version */ + version: string; + /** Artifact URI */ + uri: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/autoscalingConfigurationDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/autoscalingConfigurationDto.ts new file mode 100644 index 000000000..1cd3893ed --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/autoscalingConfigurationDto.ts @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { ScalingDetails } from "./scalingDetails"; + +/** + * Data Transfer Object(DTO) representing autoscaling configuration + */ +export interface AutoscalingConfigurationDto { + /** Configuration for scaling up */ + scaleUpDetails?: ScalingDetails; + /** Configuration for scaling down */ + scaleDownDetails?: ScalingDetails; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/containerEnvironmentEntryDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/containerEnvironmentEntryDto.ts new file mode 100644 index 000000000..18c57c5d9 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/containerEnvironmentEntryDto.ts @@ -0,0 +1,33 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Data Transfer Object(DTO) representing a container environment entry + */ +export interface ContainerEnvironmentEntryDto { + /** Container environment key */ + key: string; + /** Container environment value */ + value: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatus.ts b/src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatus.ts new file mode 100644 index 000000000..12f5f0b1d --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatus.ts @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * NVCF UI Shim API + * API for the NVCF UI shim service + * OpenAPI spec version: 0.1.0 + */ +import type { ControlPlaneComponentStatusStatus } from "./controlPlaneComponentStatusStatus"; + +/** + * Health status of a single control plane component + */ +export interface ControlPlaneComponentStatus { + /** Name of the control plane component */ + componentName: string; + /** Kubernetes namespace the component lives in */ + namespace: string; + /** Health status of the component */ + status: ControlPlaneComponentStatusStatus; + /** Time at which the status was recorded (RFC 3339 / ISO 8601) */ + timestamp: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatusStatus.ts b/src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatusStatus.ts new file mode 100644 index 000000000..dcef24d00 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/controlPlaneComponentStatusStatus.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * NVCF UI Shim API + * API for the NVCF UI shim service + * OpenAPI spec version: 0.1.0 + */ + +/** + * Health status of the component + */ +export type ControlPlaneComponentStatusStatus = + (typeof ControlPlaneComponentStatusStatus)[keyof typeof ControlPlaneComponentStatusStatus]; + +export const ControlPlaneComponentStatusStatus = { + healthy: "healthy", + unhealthy: "unhealthy", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/deploymentHealthDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/deploymentHealthDto.ts new file mode 100644 index 000000000..0cabdbfc6 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/deploymentHealthDto.ts @@ -0,0 +1,52 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Data Transfer Object(DTO) representing deployment error + */ +export interface DeploymentHealthDto { + /** + * ICMS Request ID + * @minLength 36 + * @maxLength 36 + */ + icmsRequestId: string; + /** + * GPU Type as per SDD + * @minLength 1 + */ + gpu: string; + /** + * Backend/CSP where the GPU powered instance will be launched + * @minLength 1 + */ + backend: string; + /** Instance type */ + instanceType?: string; + /** + * Deployment error + * @minLength 1 + */ + error: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/deploymentResponse.ts b/src/uis/nvcf-ui/ui/src/generated/model/deploymentResponse.ts new file mode 100644 index 000000000..7068ef920 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/deploymentResponse.ts @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { FunctionDeploymentDto } from "./functionDeploymentDto"; + +/** + * Function Deployment Response + */ +export interface DeploymentResponse { + /** Deployment details */ + deployment: FunctionDeploymentDto; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDto.ts new file mode 100644 index 000000000..d221bf17e --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDto.ts @@ -0,0 +1,78 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { DeploymentHealthDto } from "./deploymentHealthDto"; +import type { FunctionDeploymentDtoFunctionStatus } from "./functionDeploymentDtoFunctionStatus"; +import type { GpuSpecificationDto } from "./gpuSpecificationDto"; + +/** + * Function deployment response + */ +export interface FunctionDeploymentDto { + /** + * Function id + * @minLength 36 + * @maxLength 36 + */ + functionId: string; + /** + * Function version id + * @minLength 36 + * @maxLength 36 + */ + functionVersionId: string; + /** + * Last deployment id + * @minLength 36 + * @maxLength 36 + */ + deploymentId: string; + /** + * Function name + * @minLength 1 + */ + functionName: string; + /** + * NVIDIA Cloud Account Id + * @minLength 1 + */ + ncaId: string; + /** Function status */ + functionStatus: FunctionDeploymentDtoFunctionStatus; + /** + * Health info for a deployment specification is included only if there are any + issues/errors. + + * @minItems 0 + */ + healthInfo?: DeploymentHealthDto[]; + /** + * Function deployment details + * @minItems 0 + */ + deploymentSpecifications: GpuSpecificationDto[]; + /** Function deployment creation timestamp */ + createdAt: string; + /** Function deployment modification timestamp */ + lastUpdatedAt: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDtoFunctionStatus.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDtoFunctionStatus.ts new file mode 100644 index 000000000..66d13e098 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionDeploymentDtoFunctionStatus.ts @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Function status + */ +export type FunctionDeploymentDtoFunctionStatus = + (typeof FunctionDeploymentDtoFunctionStatus)[keyof typeof FunctionDeploymentDtoFunctionStatus]; + +export const FunctionDeploymentDtoFunctionStatus = { + ACTIVE: "ACTIVE", + DEPLOYING: "DEPLOYING", + ERROR: "ERROR", + INACTIVE: "INACTIVE", + DELETED: "DELETED", + DEGRADED: "DEGRADED", + DEGRADING: "DEGRADING", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionDto.ts new file mode 100644 index 000000000..3b3df2471 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionDto.ts @@ -0,0 +1,126 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { ArtifactDto } from "./artifactDto"; +import type { ContainerEnvironmentEntryDto } from "./containerEnvironmentEntryDto"; +import type { FunctionDtoApiBodyFormat } from "./functionDtoApiBodyFormat"; +import type { FunctionDtoFunctionType } from "./functionDtoFunctionType"; +import type { FunctionDtoStatus } from "./functionDtoStatus"; +import type { FunctionModelDto } from "./functionModelDto"; +import type { HealthDto } from "./healthDto"; +import type { InstanceDto } from "./instanceDto"; +import type { RateLimitDto } from "./rateLimitDto"; +import type { TelemetriesDto } from "./telemetriesDto"; + +/** + * Data Transfer Object (DTO) representing a function + */ +export interface FunctionDto { + /** + * Unique function id + * @minLength 36 + * @maxLength 36 + */ + id: string; + /** NVIDIA Cloud Account Id */ + ncaId: string; + /** + * Unique function version id + * @minLength 36 + * @maxLength 36 + */ + versionId: string; + /** Function name */ + name: string; + /** Function status */ + status: FunctionDtoStatus; + /** Entrypoint for invoking the container to process requests */ + inferenceUrl?: string; + /** Indicates whether the function is owned by another account. If the account + that is being used to lookup functions happens to be authorized to invoke/list + this function which is owned by a different account, then this field is set + to true and ncaId will contain the id of the account that owns the function. + Otherwise, this field is not set as it defaults to false. + */ + ownedByDifferentAccount?: boolean; + /** Optional port number where the inference listener is running - defaults to 8000 for Triton */ + inferencePort?: number; + /** Args used to launch the container */ + containerArgs?: string; + /** + * Environment settings used to launch the container + * @minItems 0 + */ + containerEnvironment?: ContainerEnvironmentEntryDto[]; + /** + * Optional list of models + * @minItems 0 + */ + models?: FunctionModelDto[]; + /** Optional custom container */ + containerImage?: string; + /** Invocation request body format */ + apiBodyFormat?: FunctionDtoApiBodyFormat; + /** Optional Helm Chart */ + helmChart?: string; + /** Helm Chart Service Name specified only when helmChart property is specified + */ + helmChartServiceName?: string; + /** + * Health endpoint for the container or helmChart. Deprecated, use health.uri instead. + * @deprecated + */ + healthUri: string; + /** Function creation timestamp */ + createdAt: string; + /** + * List of active instances for this function. + * @minItems 0 + */ + activeInstances?: InstanceDto[]; + /** + * Optional set of resources. + * @minItems 0 + */ + resources?: ArtifactDto[]; + /** + * Optional set of tags. Maximum allowed number of tags per function is 64. Maximum length of each tag is 128 chars. + * @minItems 0 + */ + tags?: string[]; + /** Function/version description */ + description?: string; + /** Function health configuration */ + health?: HealthDto; + /** Used to indicate a STREAMING function. Defaults to DEFAULT. */ + functionType: FunctionDtoFunctionType; + /** + * Optional secret names + * @minItems 0 + */ + secrets?: string[]; + /** Optional rate limit policy */ + rateLimit?: RateLimitDto; + /** Optional telemetry configuration for the function */ + telemetries?: TelemetriesDto; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionDtoApiBodyFormat.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionDtoApiBodyFormat.ts new file mode 100644 index 000000000..be30b1555 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionDtoApiBodyFormat.ts @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Invocation request body format + */ +export type FunctionDtoApiBodyFormat = + (typeof FunctionDtoApiBodyFormat)[keyof typeof FunctionDtoApiBodyFormat]; + +export const FunctionDtoApiBodyFormat = { + PREDICT_V2: "PREDICT_V2", + CUSTOM: "CUSTOM", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionDtoFunctionType.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionDtoFunctionType.ts new file mode 100644 index 000000000..274eb4008 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionDtoFunctionType.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Used to indicate a STREAMING function. Defaults to DEFAULT. + */ +export type FunctionDtoFunctionType = + (typeof FunctionDtoFunctionType)[keyof typeof FunctionDtoFunctionType]; + +export const FunctionDtoFunctionType = { + DEFAULT: "DEFAULT", + STREAMING: "STREAMING", + LLM: "LLM", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionDtoStatus.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionDtoStatus.ts new file mode 100644 index 000000000..2853c7a01 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionDtoStatus.ts @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Function status + */ +export type FunctionDtoStatus = + (typeof FunctionDtoStatus)[keyof typeof FunctionDtoStatus]; + +export const FunctionDtoStatus = { + ACTIVE: "ACTIVE", + DEPLOYING: "DEPLOYING", + ERROR: "ERROR", + INACTIVE: "INACTIVE", + DELETED: "DELETED", + DEGRADED: "DEGRADED", + DEGRADING: "DEGRADING", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionModelDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionModelDto.ts new file mode 100644 index 000000000..0ebad500e --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionModelDto.ts @@ -0,0 +1,38 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { LlmConfigDto } from "./llmConfigDto"; + +/** + * Model associated with a function, including optional LLM routing metadata + */ +export interface FunctionModelDto { + /** Model name */ + name: string; + /** Model version. Optional for LLM-type functions. */ + version?: string; + /** Model URI. Optional for LLM-type functions. */ + uri?: string; + /** LLM-specific configuration for this model. Only relevant for LLM-type functions. */ + llmConfig?: LlmConfigDto; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/functionResponse.ts b/src/uis/nvcf-ui/ui/src/generated/model/functionResponse.ts new file mode 100644 index 000000000..7dfeb6869 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/functionResponse.ts @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { FunctionDto } from "./functionDto"; + +/** + * Response body with function details + */ +export interface FunctionResponse { + /** Function details */ + function: FunctionDto; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsParams.ts b/src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsParams.ts new file mode 100644 index 000000000..4290602a2 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsParams.ts @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { GetAllFunctionsVisibilityItem } from "./getAllFunctionsVisibilityItem"; + +export type GetAllFunctionsParams = { + /** + * Query param 'visibility' indicates the kind of functions to be included + in the response. + + * @minItems 0 + */ + visibility?: GetAllFunctionsVisibilityItem[]; +}; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsVisibilityItem.ts b/src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsVisibilityItem.ts new file mode 100644 index 000000000..d988b7d06 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getAllFunctionsVisibilityItem.ts @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +export type GetAllFunctionsVisibilityItem = + (typeof GetAllFunctionsVisibilityItem)[keyof typeof GetAllFunctionsVisibilityItem]; + +export const GetAllFunctionsVisibilityItem = { + authorized: "authorized", + private: "private", + public: "public", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponse.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponse.ts new file mode 100644 index 000000000..416dc2d74 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponse.ts @@ -0,0 +1,91 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ +import type { GetClusterResponseCloudProvider } from "./getClusterResponseCloudProvider"; +import type { GetClusterResponseClusterConfigurationFiles } from "./getClusterResponseClusterConfigurationFiles"; +import type { GetClusterResponseClusterConfigurations } from "./getClusterResponseClusterConfigurations"; +import type { GetClusterResponseClusterUpgradeStatus } from "./getClusterResponseClusterUpgradeStatus"; +import type { GetClusterResponseGpuUsage } from "./getClusterResponseGpuUsage"; +import type { GetClusterResponseStatus } from "./getClusterResponseStatus"; +import type { GpuResponseSchema } from "./gpuResponseSchema"; +import type { ImageCredentialHelper } from "./imageCredentialHelper"; +import type { SisConfig } from "./sisConfig"; +import type { VaultConfig } from "./vaultConfig"; + +/** + * Get Cluster Response + */ +export interface GetClusterResponse { + /** Name of the cluster */ + clusterName?: string; + /** Group to which the custer belongs */ + clusterGroupName?: string; + /** Description of the cluster */ + clusterDescription?: string; + /** NVIDIA Cloud Account ID */ + ncaId?: string; + /** AuthorizedNcaIds for the cluster */ + authorizedNCAIds?: string[]; + /** Cloud Provider for the cluster */ + cloudProvider?: GetClusterResponseCloudProvider; + /** Region of the cluster */ + region?: string; + /** Capabilities of the cluster */ + capabilities?: string[]; + /** Attributes related to the cluster */ + attributes?: string[]; + /** Custom attributes related to the cluster */ + customAttributes?: string[]; + /** GPUs supported in cluster */ + gpus?: GpuResponseSchema[]; + /** Nvca version */ + nvcaVersion?: string; + /** Cluster ID */ + clusterId?: string; + /** Cluster Group ID */ + clusterGroupId?: string; + /** Cluster status */ + status?: GetClusterResponseStatus; + /** Cluster Source */ + clusterSource?: string; + /** NVCA last connected timestamp */ + nvcaLastConnected?: string; + /** K8s version on cluster */ + k8sVersion?: string; + /** Gpu usage shared by cluster */ + gpuUsage?: GetClusterResponseGpuUsage; + /** Sis Config */ + sisConfig?: SisConfig; + /** Vault Config */ + vaultConfig?: VaultConfig; + /** Image Credential Helper Config */ + imageCredentialHelper?: ImageCredentialHelper; + /** Cluster upgrade status */ + clusterUpgradeStatus?: GetClusterResponseClusterUpgradeStatus; + /** Cluster key Id */ + clusterKeyId?: string; + /** Advanced BYOC cluster configurations as key-value string map */ + clusterConfigurations?: GetClusterResponseClusterConfigurations; + /** Advanced BYOC cluster configuration files as base64-encoded string map */ + clusterConfigurationFiles?: GetClusterResponseClusterConfigurationFiles; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseCloudProvider.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseCloudProvider.ts new file mode 100644 index 000000000..99c4a368c --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseCloudProvider.ts @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * Cloud Provider for the cluster + */ +export type GetClusterResponseCloudProvider = + (typeof GetClusterResponseCloudProvider)[keyof typeof GetClusterResponseCloudProvider]; + +export const GetClusterResponseCloudProvider = { + "ON-PREM": "ON-PREM", + "DGX-CLOUD": "DGX-CLOUD", + AWS: "AWS", + OCI: "OCI", + AZURE: "AZURE", + GDN: "GDN", + GFN: "GFN", + GCP: "GCP", + NCP: "NCP", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurationFiles.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurationFiles.ts new file mode 100644 index 000000000..da847b67a --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurationFiles.ts @@ -0,0 +1,30 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * Advanced BYOC cluster configuration files as base64-encoded string map + */ +export type GetClusterResponseClusterConfigurationFiles = { + [key: string]: string; +}; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurations.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurations.ts new file mode 100644 index 000000000..208ddac5e --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterConfigurations.ts @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * Advanced BYOC cluster configurations as key-value string map + */ +export type GetClusterResponseClusterConfigurations = { [key: string]: string }; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterUpgradeStatus.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterUpgradeStatus.ts new file mode 100644 index 000000000..c46d95af5 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseClusterUpgradeStatus.ts @@ -0,0 +1,37 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * Cluster upgrade status + */ +export type GetClusterResponseClusterUpgradeStatus = + (typeof GetClusterResponseClusterUpgradeStatus)[keyof typeof GetClusterResponseClusterUpgradeStatus]; + +export const GetClusterResponseClusterUpgradeStatus = { + IN_PROGRESS: "IN_PROGRESS", + AVAILABLE: "AVAILABLE", + PAUSED: "PAUSED", + SUCCESS: "SUCCESS", + FAILED: "FAILED", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseGpuUsage.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseGpuUsage.ts new file mode 100644 index 000000000..62d126ee3 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseGpuUsage.ts @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ +import type { GpuCapacity } from "./gpuCapacity"; + +/** + * Gpu usage shared by cluster + */ +export type GetClusterResponseGpuUsage = { [key: string]: GpuCapacity }; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseStatus.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseStatus.ts new file mode 100644 index 000000000..a6e5748d1 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClusterResponseStatus.ts @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * Cluster status + */ +export type GetClusterResponseStatus = + (typeof GetClusterResponseStatus)[keyof typeof GetClusterResponseStatus]; + +export const GetClusterResponseStatus = { + READY: "READY", + PAUSED: "PAUSED", + FAILED: "FAILED", + ABANDONED: "ABANDONED", + NOT_READY: "NOT_READY", + UNHEALTHY: "UNHEALTHY", + DELETED: "DELETED", + CORDON: "CORDON", + CORDON_AND_DRAIN: "CORDON_AND_DRAIN", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getClustersParams.ts b/src/uis/nvcf-ui/ui/src/generated/model/getClustersParams.ts new file mode 100644 index 000000000..004e3f7f6 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getClustersParams.ts @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +export type GetClustersParams = { + /** + * Set this param to include all clusters authorized to given ncaId + */ + includeAuthorizedClusters?: boolean; + /** + * If includeAuthorizedClusters=true then this param will be considered else ignored + */ + includeGfnInAuthorizedClusters?: boolean; +}; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/getFunctionVersionParams.ts b/src/uis/nvcf-ui/ui/src/generated/model/getFunctionVersionParams.ts new file mode 100644 index 000000000..96a147cf9 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/getFunctionVersionParams.ts @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +export type GetFunctionVersionParams = { + /** + * Query param 'includeSecrets' indicates whether to include secret names for + the function in the response. + + */ + includeSecrets?: boolean; +}; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/gpuCapacity.ts b/src/uis/nvcf-ui/ui/src/generated/model/gpuCapacity.ts new file mode 100644 index 000000000..24ff34503 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/gpuCapacity.ts @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +export interface GpuCapacity { + capacity?: number; + allocated?: number; + available?: number; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/gpuResponseSchema.ts b/src/uis/nvcf-ui/ui/src/generated/model/gpuResponseSchema.ts new file mode 100644 index 000000000..67c4cce76 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/gpuResponseSchema.ts @@ -0,0 +1,36 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ +import type { InstanceTypeResponseSchema } from "./instanceTypeResponseSchema"; + +export interface GpuResponseSchema { + /** + * Name of the gpu + * @minLength 1 + */ + name: string; + /** Total gpu capacity */ + capacity?: number; + /** List of instance types for that Gpu */ + instanceTypes?: InstanceTypeResponseSchema[]; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDto.ts new file mode 100644 index 000000000..906a0727f --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDto.ts @@ -0,0 +1,105 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { AutoscalingConfigurationDto } from "./autoscalingConfigurationDto"; +import type { GpuSpecificationDtoConfiguration } from "./gpuSpecificationDtoConfiguration"; +import type { HelmValidationPolicyDto } from "./helmValidationPolicyDto"; + +/** + * Data Transfer Object(DTO) representing GPU specification. + */ +export interface GpuSpecificationDto { + /** + * GPU specification id + * @minLength 36 + * @maxLength 36 + */ + gpuSpecificationId?: string; + /** + * GPU name from the cluster + * @minLength 1 + */ + gpu: string; + /** Use clusters instead of this property. Legacy property typically used when deploying a function to GFN. */ + backend?: string; + /** Maximum number of instances for the deployment */ + maxInstances: number; + /** Minimum number of instances for the deployment */ + minInstances: number; + /** + * Instance type, based on GPU, assigned to a Worker + * @minLength 1 + */ + instanceType: string; + /** + * List of availability-zones(or clusters) in the cluster group + * @minItems 0 + */ + availabilityZones?: string[]; + /** + * Max request concurrency between 1 (default) and 16384. + * @minimum 1 + * @maximum 16384 + */ + maxRequestConcurrency?: number; + /** Typically used when the function is based on Helm Charts to substitute + placeholders in values yaml. + */ + configuration?: GpuSpecificationDtoConfiguration; + /** + * Specific clusters powered by the selected instance-type to deploy function. + + * @minItems 0 + */ + clusters?: string[]; + /** + * List of regions allowed to deploy. The instance or worker node will be in one of + the specified geographical regions. + + * @minItems 0 + */ + regions?: string[]; + /** + * Specific cluster attributes/capabilities to deploy functions. For example, + HIPPA Compliant, Confidential Compute Compliant, etc. + + * @minItems 0 + */ + attributes?: string[]; + /** CPU Architecture details */ + cpuArch?: string; + /** Operating System details */ + os?: string; + /** GPU driver version */ + driverVersion?: string; + /** Amount of available storage, e.g. 80G */ + storage?: string; + /** Amount of RAM */ + systemMemory?: string; + /** Amount of GPU memory */ + gpuMemory?: string; + /** Customizable autoscaling configuration */ + autoscalingConfiguration?: AutoscalingConfigurationDto; + /** Helm validation policy */ + helmValidationPolicy?: HelmValidationPolicyDto; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDtoConfiguration.ts b/src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDtoConfiguration.ts new file mode 100644 index 000000000..f2c32bd5f --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/gpuSpecificationDtoConfiguration.ts @@ -0,0 +1,30 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Typically used when the function is based on Helm Charts to substitute + placeholders in values yaml. + + */ +export type GpuSpecificationDtoConfiguration = { [key: string]: unknown }; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/healthDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/healthDto.ts new file mode 100644 index 000000000..e113dfd82 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/healthDto.ts @@ -0,0 +1,40 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { HealthDtoProtocol } from "./healthDtoProtocol"; + +/** + * Data Transfer Object(DTO) representing a function ne + */ +export interface HealthDto { + /** HTTP/gPRC protocol type for health endpoint */ + protocol: HealthDtoProtocol; + /** Health endpoint for the container or the helmChart */ + uri: string; + /** Port number where the health listener is running */ + port: number; + /** ISO 8601 duration string in PnDTnHnMn.nS format */ + timeout: string; + /** Expected return status code considered as successful. */ + expectedStatusCode: number; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/healthDtoProtocol.ts b/src/uis/nvcf-ui/ui/src/generated/model/healthDtoProtocol.ts new file mode 100644 index 000000000..630d20491 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/healthDtoProtocol.ts @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * HTTP/gPRC protocol type for health endpoint + */ +export type HealthDtoProtocol = + (typeof HealthDtoProtocol)[keyof typeof HealthDtoProtocol]; + +export const HealthDtoProtocol = { + HTTP: "HTTP", + gRPC: "gRPC", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDto.ts new file mode 100644 index 000000000..f7ad393c5 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDto.ts @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { HelmValidationPolicyDtoName } from "./helmValidationPolicyDtoName"; +import type { KubernetesType } from "./kubernetesType"; + +/** + * Data Transfer Object(DTO) representing Helm validation policy + */ +export interface HelmValidationPolicyDto { + /** Helm validation policy name. */ + name: HelmValidationPolicyDtoName; + /** + * An API Group in Kubernetes is a collection of related functionality. + When present, must contain at least one entry; each entry must be a valid + KubernetesType (group, version, kind). + + * @minItems 1 + */ + extraKubernetesTypes?: KubernetesType[]; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDtoName.ts b/src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDtoName.ts new file mode 100644 index 000000000..8043a45b6 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/helmValidationPolicyDtoName.ts @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Helm validation policy name. + */ +export type HelmValidationPolicyDtoName = + (typeof HelmValidationPolicyDtoName)[keyof typeof HelmValidationPolicyDtoName]; + +export const HelmValidationPolicyDtoName = { + Default: "Default", + Unrestricted: "Unrestricted", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/imageConfig.ts b/src/uis/nvcf-ui/ui/src/generated/model/imageConfig.ts new file mode 100644 index 000000000..fbf7b20a6 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/imageConfig.ts @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * ImageConfig + */ +export interface ImageConfig { + /** + * Image repository + * @minLength 1 + */ + repository: string; + /** + * Image tag + * @minLength 1 + */ + tag: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/imageCredentialHelper.ts b/src/uis/nvcf-ui/ui/src/generated/model/imageCredentialHelper.ts new file mode 100644 index 000000000..0930bdca5 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/imageCredentialHelper.ts @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ +import type { ImageConfig } from "./imageConfig"; + +/** + * ImageCredentialHelper + */ +export interface ImageCredentialHelper { + /** Image config for credential helper */ + imageConfig?: ImageConfig; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/instanceDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/instanceDto.ts new file mode 100644 index 000000000..2594a6489 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/instanceDto.ts @@ -0,0 +1,66 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { InstanceDtoInstanceStatus } from "./instanceDtoInstanceStatus"; + +/** + * Data Transfer Object(DTO) representing an instance + */ +export interface InstanceDto { + /** Unique id of the instance */ + instanceId?: string; + /** + * Function executing on the instance + * @minLength 36 + * @maxLength 36 + */ + functionId?: string; + /** + * Function version executing on the instance + * @minLength 36 + * @maxLength 36 + */ + functionVersionId?: string; + /** GPU instance-type powering the instance */ + instanceType?: string; + /** Instance status */ + instanceStatus?: InstanceDtoInstanceStatus; + /** + * ICMS request-id used to launch this instance + * @minLength 36 + * @maxLength 36 + */ + icmsRequestId?: string; + /** NVIDIA Cloud Account Id that owns the function running on the instance */ + ncaId?: string; + /** GPU name powering the instance */ + gpu?: string; + /** Backend where the instance is running */ + backend?: string; + /** Location such as zone name or region where the instance is running */ + location?: string; + /** Instance creation timestamp */ + instanceCreatedAt?: string; + /** Instance's last updated timestamp */ + instanceUpdatedAt?: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/instanceDtoInstanceStatus.ts b/src/uis/nvcf-ui/ui/src/generated/model/instanceDtoInstanceStatus.ts new file mode 100644 index 000000000..6e9134e40 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/instanceDtoInstanceStatus.ts @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Instance status + */ +export type InstanceDtoInstanceStatus = + (typeof InstanceDtoInstanceStatus)[keyof typeof InstanceDtoInstanceStatus]; + +export const InstanceDtoInstanceStatus = { + ACTIVE: "ACTIVE", + STARTING: "STARTING", + RUNNING: "RUNNING", + ERRORED: "ERRORED", + PREEMPTED: "PREEMPTED", + DELETED: "DELETED", + TERMINATED: "TERMINATED", +} as const; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/instanceTypeResponseSchema.ts b/src/uis/nvcf-ui/ui/src/generated/model/instanceTypeResponseSchema.ts new file mode 100644 index 000000000..e375169ab --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/instanceTypeResponseSchema.ts @@ -0,0 +1,56 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +export interface InstanceTypeResponseSchema { + /** Number of cpu cores for the instance type */ + cpuCores?: number; + /** Memory of the System. e.g. 24G */ + systemMemory?: string; + /** Memory of the GPU. e.g. 24G */ + gpuMemory?: string; + /** Number of GPUs to be attached to the worker pod. Supported values 1, 2, 4, 8 (Default value is 1) */ + gpuCount?: number; + /** + * Name of the instance type + * @minLength 1 + */ + name: string; + /** Description of the instance type */ + description?: string; + /** + * Value of the instance type + * @minLength 1 + */ + value: string; + /** Architecture details of the CPU */ + cpuArch?: string; + /** Operating system details */ + os?: string; + /** GPU driver version */ + driverVersion?: string; + /** The amount of available storage, e.g. 80G */ + storage?: string; + /** Indicates if its the default instance type for the GPU */ + default?: boolean; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/kubernetesType.ts b/src/uis/nvcf-ui/ui/src/generated/model/kubernetesType.ts new file mode 100644 index 000000000..7725dc02e --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/kubernetesType.ts @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +export interface KubernetesType { + /** + * Name of API Group + * @minLength 1 + */ + group: string; + /** + * Version of API Group + * @minLength 1 + */ + version: string; + /** + * API Group resource or Kind + * @minLength 1 + */ + kind: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/listAccountResponse.ts b/src/uis/nvcf-ui/ui/src/generated/model/listAccountResponse.ts new file mode 100644 index 000000000..fcd003c8a --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/listAccountResponse.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { AccountDto } from "./accountDto"; + +/** + * Response body containing list of NVIDIA Cloud Accounts + */ +export interface ListAccountResponse { + /** + * List of NVIDIA Cloud Accounts + * @minItems 0 + */ + cloudAccounts: AccountDto[]; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/listDeploymentsResponse.ts b/src/uis/nvcf-ui/ui/src/generated/model/listDeploymentsResponse.ts new file mode 100644 index 000000000..d99dfa3e8 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/listDeploymentsResponse.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { FunctionDeploymentDto } from "./functionDeploymentDto"; + +/** + * Function Deployments Response + */ +export interface ListDeploymentsResponse { + /** + * List of deployments + * @minItems 0 + */ + deployments: FunctionDeploymentDto[]; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/listFunctionsResponse.ts b/src/uis/nvcf-ui/ui/src/generated/model/listFunctionsResponse.ts new file mode 100644 index 000000000..9372ad418 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/listFunctionsResponse.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { FunctionDto } from "./functionDto"; + +/** + * Response body containing list of functions + */ +export interface ListFunctionsResponse { + /** + * List of functions + * @minItems 0 + */ + functions: FunctionDto[]; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/llmConfigDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/llmConfigDto.ts new file mode 100644 index 000000000..e0d58a8e6 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/llmConfigDto.ts @@ -0,0 +1,40 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * LLM-specific configuration for a model + */ +export interface LlmConfigDto { + /** + * OpenAI-compatible URIs supported by this model (e.g. /v1/chat/completions). Required for LLM models. + * @minItems 1 + */ + uris: string[]; + /** Token-level rate limit for this model. */ + tokenRateLimit?: string; + /** Tokenizer identifier for this model. */ + tokenizer?: string; + /** Routing method for this model. */ + routingMethod?: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/rateLimitDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/rateLimitDto.ts new file mode 100644 index 000000000..e7968f0ee --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/rateLimitDto.ts @@ -0,0 +1,50 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { RateLimitDtoPerNcaIdRate } from "./rateLimitDtoPerNcaIdRate"; + +/** + * Data Transfer Object(DTO) representing Rate limit config + */ +export interface RateLimitDto { + /** + * Rate + * @pattern ^(?!.*-([SMHD]).*-\1)[1-9]\d*-[SMHD](,\s*[1-9]\d*-[SMHD])*$ + */ + rateLimit?: string; + /** + * NCA ID Exemptions + * @minItems 0 + * @maxItems 32 + */ + exemptedNcaIds?: string[]; + /** Per NCA ID Rate */ + perNcaIdRate?: RateLimitDtoPerNcaIdRate; + /** Sync check. Defaults to false */ + syncCheck?: boolean; + /** + * Per-user rate, intended for traffic authenticated with an invocation delegation token (InvocationToken). When set, this single rate is enforced individually against every unique caller identity, in addition to the NCA-tier limit. + * @pattern ^(?!.*-([SMHD]).*-\1)[1-9]\d*-[SMHD](,\s*[1-9]\d*-[SMHD])*$ + */ + perUserRate?: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/rateLimitDtoPerNcaIdRate.ts b/src/uis/nvcf-ui/ui/src/generated/model/rateLimitDtoPerNcaIdRate.ts new file mode 100644 index 000000000..92095e305 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/rateLimitDtoPerNcaIdRate.ts @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Per NCA ID Rate + */ +export type RateLimitDtoPerNcaIdRate = { [key: string]: string }; diff --git a/src/uis/nvcf-ui/ui/src/generated/model/scalingDetails.ts b/src/uis/nvcf-ui/ui/src/generated/model/scalingDetails.ts new file mode 100644 index 000000000..446d56c75 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/scalingDetails.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ +import type { StickinessWindow } from "./stickinessWindow"; + +export interface ScalingDetails { + /** Scaling metric */ + metric?: string; + /** Scaling factor must be greater-than 1.0 for scale up and less-than 1.0 for scale down. This factor is used to multiply the currentinstance count when specified threshold is met. */ + factor: number; + /** Scaling threshold (0-100) as a percentage of utilizationupon which the number of current instances are multiplied with thespecified factor. */ + threshold: number; + /** Stickiness window configuration */ + stickiness?: StickinessWindow; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/sisConfig.ts b/src/uis/nvcf-ui/ui/src/generated/model/sisConfig.ts new file mode 100644 index 000000000..d54e6af0b --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/sisConfig.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * SisConfig + */ +export interface SisConfig { + /** Public Keyset Endpoint */ + publicKeysetEndpoint?: string; + /** Token Endpoint URL */ + tokenURL?: string; + /** Spot Instance Service Endpoint URL */ + spotServiceURL?: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/stickinessWindow.ts b/src/uis/nvcf-ui/ui/src/generated/model/stickinessWindow.ts new file mode 100644 index 000000000..f52a717e3 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/stickinessWindow.ts @@ -0,0 +1,30 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +export interface StickinessWindow { + /** Window size must be less than or equal to PT1H and will berounded to the nearest minute */ + size: string; + /** Window threshold must be less than size */ + threshold: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/telemetriesDto.ts b/src/uis/nvcf-ui/ui/src/generated/model/telemetriesDto.ts new file mode 100644 index 000000000..2ef81339f --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/telemetriesDto.ts @@ -0,0 +1,47 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Cloud Functions + * OpenAPI spec version: 2.245.7 + */ + +/** + * Telemetry configuration for logs, metrics, and traces. + */ +export interface TelemetriesDto { + /** + * UUID representing the logs telemetry. + * @minLength 36 + * @maxLength 36 + */ + logsTelemetryId?: string; + /** + * UUID representing the metrics telemetry. + * @minLength 36 + * @maxLength 36 + */ + metricsTelemetryId?: string; + /** + * UUID representing the traces telemetry. + * @minLength 36 + * @maxLength 36 + */ + tracesTelemetryId?: string; +} diff --git a/src/uis/nvcf-ui/ui/src/generated/model/vaultConfig.ts b/src/uis/nvcf-ui/ui/src/generated/model/vaultConfig.ts new file mode 100644 index 000000000..ed4b9a50c --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/generated/model/vaultConfig.ts @@ -0,0 +1,31 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Generated by orval v8.9.0 🍺 + * Do not edit manually. + * Gfn Spot + * OpenAPI spec version: 1.0 + */ + +/** + * VaultConfig + */ +export interface VaultConfig { + /** Vault address */ + address?: string; +} diff --git a/src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.test.ts b/src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.test.ts new file mode 100644 index 000000000..1b9d73c41 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.test.ts @@ -0,0 +1,77 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { act, renderHook } from "@testing-library/react"; +import { beforeEach, describe, expect, it } from "vitest"; +import { readLocalStorage, useLocalStorage } from "./useLocalStorage"; + +const KEY = "test-key"; + +beforeEach(() => { + localStorage.clear(); +}); + +describe("readLocalStorage", () => { + it("returns the fallback when nothing is stored", () => { + expect(readLocalStorage(KEY, "fallback")).toBe("fallback"); + }); + + it("deserializes a stored value", () => { + localStorage.setItem(KEY, JSON.stringify({ a: 1 })); + expect(readLocalStorage(KEY, null)).toEqual({ a: 1 }); + }); + + it("returns a plain (non-JSON) string as-is", () => { + localStorage.setItem(KEY, "plain-string"); + expect(readLocalStorage(KEY, null)).toBe("plain-string"); + }); +}); + +describe("useLocalStorage", () => { + it("returns the fallback when nothing is stored", () => { + const { result } = renderHook(() => useLocalStorage(KEY, "fallback")); + expect(result.current[0]).toBe("fallback"); + }); + + it("reads an existing stored value", () => { + localStorage.setItem(KEY, JSON.stringify("stored")); + const { result } = renderHook(() => + useLocalStorage(KEY, null), + ); + expect(result.current[0]).toBe("stored"); + }); + + it("persists and re-renders with the new value when set", () => { + const { result } = renderHook(() => + useLocalStorage(KEY, null), + ); + + act(() => result.current[1]("next")); + + expect(result.current[0]).toBe("next"); + expect(localStorage.getItem(KEY)).toBe(JSON.stringify("next")); + }); + + it("syncs across hook instances in the same tab", () => { + const a = renderHook(() => useLocalStorage(KEY, null)); + const b = renderHook(() => useLocalStorage(KEY, null)); + + act(() => a.result.current[1]("shared")); + + expect(b.result.current[0]).toBe("shared"); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.ts b/src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.ts new file mode 100644 index 000000000..19017db94 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/hooks/useLocalStorage.ts @@ -0,0 +1,72 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useCallback, useMemo, useSyncExternalStore } from "react"; + +// Same-tab writes notify via a privately-named event so they don't pollute the +// native `storage` channel; cross-tab writes arrive on `storage` itself. +const SYNC_EVENT = "nvcf-local-storage"; + +function parse(raw: string | null, fallback: T): T { + if (raw === null) return fallback; + try { + return JSON.parse(raw) as T; + } catch { + // Not JSON (e.g. a plain string written elsewhere) — return it as-is. + return raw as T; + } +} + +/** Read a stored value outside React (e.g. a route `beforeLoad`). */ +export function readLocalStorage(key: string, fallback: T): T { + return parse(localStorage.getItem(key), fallback); +} + +export function useLocalStorage(key: string, fallback: T) { + const subscribe = useCallback( + (onChange: () => void) => { + const onStorage = (event: StorageEvent) => { + if (event.key === key) onChange(); + }; + const onSync = (event: Event) => { + if ((event as CustomEvent).detail === key) onChange(); + }; + window.addEventListener("storage", onStorage); + window.addEventListener(SYNC_EVENT, onSync); + return () => { + window.removeEventListener("storage", onStorage); + window.removeEventListener(SYNC_EVENT, onSync); + }; + }, + [key], + ); + + // Snapshot is the raw string — a primitive, so it's referentially stable and + // won't trigger an infinite loop. Parse into the typed value in a memo. + const raw = useSyncExternalStore(subscribe, () => localStorage.getItem(key)); + const value = useMemo(() => parse(raw, fallback), [raw, fallback]); + + const setValue = useCallback( + (next: T) => { + localStorage.setItem(key, JSON.stringify(next)); + window.dispatchEvent(new CustomEvent(SYNC_EVENT, { detail: key })); + }, + [key], + ); + + return [value, setValue] as const; +} diff --git a/src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.test.ts b/src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.test.ts new file mode 100644 index 000000000..d82856b1a --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.test.ts @@ -0,0 +1,92 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { renderHook } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { BaseTableSearch } from "~/components/DataTable/utils/tableSearchSchema"; +import { useTableUrlSync } from "./useTableUrlSync"; + +const base: BaseTableSearch = { + page: 1, + pageSize: 10, + search: "", + sort: [], + filters: [], +}; + +const navigate = vi.fn(); + +// Calls the search updater navigate received with the given state, +// returning what the new URL search params would be. +function navigatedSearch(current = base): BaseTableSearch { + const call = navigate.mock.lastCall; + if (!call) throw new Error("navigate was not called"); + return call[0].search(current); +} + +beforeEach(() => navigate.mockClear()); + +describe("useTableUrlSync", () => { + it("converts 1-indexed URL page to 0-indexed table pageIndex", () => { + const { result } = renderHook(() => + useTableUrlSync({ ...base, page: 3 }, navigate), + ); + expect(result.current.state.pagination).toMatchObject({ pageIndex: 2 }); + }); + + it("state.sorting reflects URL sort", () => { + const sort = [{ id: "createdAt", desc: true }]; + const { result } = renderHook(() => + useTableUrlSync({ ...base, sort }, navigate), + ); + expect(result.current.state.sorting).toEqual(sort); + }); + + it("state.globalFilter reflects URL search", () => { + const { result } = renderHook(() => + useTableUrlSync({ ...base, search: "hello" }, navigate), + ); + expect(result.current.state.globalFilter).toBe("hello"); + }); + + it("onGlobalFilterChange writes to search", () => { + const { result } = renderHook(() => useTableUrlSync(base, navigate)); + result.current.onGlobalFilterChange("foo"); + expect(navigatedSearch().search).toBe("foo"); + }); + + it("onSortingChange writes to sort", () => { + const { result } = renderHook(() => useTableUrlSync(base, navigate)); + const sort = [{ id: "name", desc: false }]; + result.current.onSortingChange(sort); + expect(navigatedSearch().sort).toEqual(sort); + }); + + it("onColumnFiltersChange writes to filters", () => { + const { result } = renderHook(() => useTableUrlSync(base, navigate)); + result.current.onColumnFiltersChange([{ id: "status", value: ["ACTIVE"] }]); + expect(navigatedSearch().filters).toEqual([ + { id: "status", value: ["ACTIVE"] }, + ]); + }); + + it("onPaginationChange writes to page and pageSize", () => { + const { result } = renderHook(() => useTableUrlSync(base, navigate)); + result.current.onPaginationChange({ pageIndex: 2, pageSize: 25 }); + expect(navigatedSearch()).toMatchObject({ page: 3, pageSize: 25 }); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.ts b/src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.ts new file mode 100644 index 000000000..aa23a0a44 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/hooks/useTableUrlSync.ts @@ -0,0 +1,129 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + ColumnFiltersState, + OnChangeFn, + PaginationState, + SortingState, + Updater, +} from "@tanstack/react-table"; +import { useCallback } from "react"; +import type { BaseTableSearch } from "~/components/DataTable/utils/tableSearchSchema"; + +function resolve(updater: Updater, current: T): T { + return typeof updater === "function" + ? (updater as (v: T) => T)(current) + : updater; +} + +type NavigateFn = (opts: { + search: (prev: TSearch) => TSearch; + replace?: boolean; +}) => void; + +export type TableUrlSyncResult = { + autoResetPageIndex: false; + state: { + pagination: PaginationState; + globalFilter: string; + sorting: SortingState; + columnFilters: ColumnFiltersState; + }; + onPaginationChange: OnChangeFn; + onGlobalFilterChange: OnChangeFn; + onSortingChange: OnChangeFn; + onColumnFiltersChange: OnChangeFn; +}; + +export function useTableUrlSync( + search: TSearch, + navigate: NavigateFn, +): TableUrlSyncResult { + const onPaginationChange: OnChangeFn = useCallback( + (updater) => { + navigate({ + search: (old) => { + const prev: PaginationState = { + pageIndex: old.page - 1, + pageSize: old.pageSize, + }; + const next = resolve(updater, prev); + return { ...old, page: next.pageIndex + 1, pageSize: next.pageSize }; + }, + replace: true, + }); + }, + [navigate], + ); + + const onGlobalFilterChange: OnChangeFn = useCallback( + (updater) => { + navigate({ + search: (old) => { + const next = resolve(updater, old.search); + return { ...old, search: next, page: 1 }; + }, + replace: true, + }); + }, + [navigate], + ); + + const onSortingChange: OnChangeFn = useCallback( + (updater) => { + navigate({ + search: (old) => { + const next = resolve(updater, old.sort); + return { ...old, sort: next, page: 1 }; + }, + replace: true, + }); + }, + [navigate], + ); + + const onColumnFiltersChange: OnChangeFn = useCallback( + (updater) => { + navigate({ + search: (old) => { + const next = resolve(updater, old.filters); + return { ...old, filters: next, page: 1 }; + }, + replace: true, + }); + }, + [navigate], + ); + + return { + autoResetPageIndex: false, + state: { + pagination: { + pageIndex: search.page - 1, + pageSize: search.pageSize, + }, + globalFilter: search.search, + sorting: search.sort, + columnFilters: search.filters, + }, + onPaginationChange, + onGlobalFilterChange, + onSortingChange, + onColumnFiltersChange, + }; +} diff --git a/src/uis/nvcf-ui/ui/src/hooks/useThemePreference.test.tsx b/src/uis/nvcf-ui/ui/src/hooks/useThemePreference.test.tsx new file mode 100644 index 000000000..051b566c2 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/hooks/useThemePreference.test.tsx @@ -0,0 +1,70 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ThemeProvider } from "@nvidia/foundations-react-core"; +import { act, renderHook } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { beforeEach, describe, expect, it } from "vitest"; +import { getStoredTheme, useThemePreference } from "./useThemePreference"; + +const STORAGE_KEY = "nvcf-theme"; + +function wrapper({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +beforeEach(() => { + localStorage.clear(); +}); + +describe("getStoredTheme", () => { + it("returns 'dark' when localStorage is empty", () => { + expect(getStoredTheme()).toBe("dark"); + }); + + it.each(["light", "dark", "system"])("returns '%s' when stored", (theme) => { + localStorage.setItem(STORAGE_KEY, theme); + expect(getStoredTheme()).toBe(theme); + }); + + it("returns 'dark' when stored value is invalid", () => { + localStorage.setItem(STORAGE_KEY, "invalid"); + expect(getStoredTheme()).toBe("dark"); + }); +}); + +describe("useThemePreference", () => { + it("returns the current theme from the provider", () => { + const { result } = renderHook(() => useThemePreference(), { wrapper }); + expect(result.current.themePreference).toBe("dark"); + }); + + it("updates provider and persists to localStorage", () => { + const { result } = renderHook(() => useThemePreference(), { wrapper }); + + act(() => { + result.current.setThemePreference("system"); + }); + + expect(result.current.themePreference).toBe("system"); + expect(localStorage.getItem(STORAGE_KEY)).toBe("system"); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/hooks/useThemePreference.ts b/src/uis/nvcf-ui/ui/src/hooks/useThemePreference.ts new file mode 100644 index 000000000..4655d2034 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/hooks/useThemePreference.ts @@ -0,0 +1,57 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { type Theme, useTheme } from "@nvidia/foundations-react-core"; +import { useCallback } from "react"; +import { flushSync } from "react-dom"; + +const STORAGE_KEY = "nvcf-theme"; +const DEFAULT_THEME: Theme = "dark"; + +export function getStoredTheme(): Theme { + const stored = localStorage.getItem(STORAGE_KEY) as Theme | null; + if (stored === "light" || stored === "dark" || stored === "system") { + return stored; + } + return DEFAULT_THEME; +} + +function setStoredTheme(theme: Theme) { + localStorage.setItem(STORAGE_KEY, theme); +} + +export const useThemePreference = () => { + const { unresolvedTheme, setTheme } = useTheme(); + const setThemePreference = useCallback( + (theme: Theme) => { + if (!document.startViewTransition) { + setTheme(theme); + setStoredTheme(theme); + } /* v8 ignore start */ else { + document.startViewTransition(() => { + flushSync(() => { + setTheme(theme); + setStoredTheme(theme); + }); + }); + } /* v8 ignore stop */ + }, + [setTheme], + ); + + return { themePreference: unresolvedTheme, setThemePreference }; +}; diff --git a/src/uis/nvcf-ui/ui/src/lib/fetch.test.ts b/src/uis/nvcf-ui/ui/src/lib/fetch.test.ts new file mode 100644 index 000000000..0c8c8a7c1 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/lib/fetch.test.ts @@ -0,0 +1,69 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HttpResponse, http } from "msw"; +import { describe, expect, it } from "vitest"; +import { server } from "~/mocks/server"; +import { customFetch } from "./fetch"; + +const TEST_URL = "/test"; + +describe("customFetch", () => { + it("returns parsed JSON on success", async () => { + server.use( + http.get(TEST_URL, () => HttpResponse.json({ id: 1, name: "test" })), + ); + + const data = await customFetch<{ id: number; name: string }>(TEST_URL, { + method: "GET", + }); + expect(data).toEqual({ id: 1, name: "test" }); + }); + + it("throws HttpError with status and body on non-2xx response", async () => { + server.use( + http.get(TEST_URL, () => + HttpResponse.json({ message: "forbidden" }, { status: 403 }), + ), + ); + + await expect( + customFetch(TEST_URL, { method: "GET" }), + ).rejects.toMatchObject({ + status: 403, + body: { message: "forbidden" }, + }); + }); + + it("returns null when response body is not valid JSON", async () => { + server.use( + http.get(TEST_URL, () => new HttpResponse("not json", { status: 200 })), + ); + + const data = await customFetch(TEST_URL, { method: "GET" }); + expect(data).toBeNull(); + }); + + it("returns null for 204 No Content", async () => { + server.use( + http.delete(TEST_URL, () => new HttpResponse(null, { status: 204 })), + ); + + const data = await customFetch(TEST_URL, { method: "DELETE" }); + expect(data).toBeNull(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/lib/fetch.ts b/src/uis/nvcf-ui/ui/src/lib/fetch.ts new file mode 100644 index 000000000..4fea2bf3e --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/lib/fetch.ts @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export class HttpError extends Error { + status: number; + statusText: string; + body: unknown; + + constructor(res: Response, body: unknown) { + super(`${res.status} ${res.statusText}`); + this.name = "HttpError"; + this.status = res.status; + this.statusText = res.statusText; + this.body = body; + } +} + +export async function customFetch( + url: string, + options: RequestInit, +): Promise { + const res = await fetch(url, options); + + const body = [204, 205, 304].includes(res.status) + ? null + : await res.json().catch(() => null); + + if (!res.ok) throw new HttpError(res, body); + + return body as T; +} diff --git a/src/uis/nvcf-ui/ui/src/lib/queryClient.ts b/src/uis/nvcf-ui/ui/src/lib/queryClient.ts new file mode 100644 index 000000000..b0e0b6300 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/lib/queryClient.ts @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { QueryClient } from "@tanstack/react-query"; + +/** Background poll intervals for live-status views (ms). */ +export const LIST_REFETCH_INTERVAL = 60_000; +export const DETAIL_REFETCH_INTERVAL = 30_000; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 2 * 60_000, + refetchOnWindowFocus: true, + retry: 1, + }, + }, +}); diff --git a/src/uis/nvcf-ui/ui/src/main.tsx b/src/uis/nvcf-ui/ui/src/main.tsx new file mode 100644 index 000000000..7c146da6f --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/main.tsx @@ -0,0 +1,60 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ThemeProvider } from "@nvidia/foundations-react-core"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { RouterProvider } from "@tanstack/react-router"; +import ReactDOM from "react-dom/client"; +import { queryClient } from "./lib/queryClient"; +import { router } from "./router"; + +import "./styles.css"; +import { getStoredTheme } from "./hooks/useThemePreference"; + +async function enableMocking() { + // VITE_MOCK is a string env var — only the literal "true" enables mocking, + // so VITE_MOCK="false" doesn't (a non-empty string is otherwise truthy). + if (import.meta.env.VITE_MOCK !== "true") return; + const { createWorker } = await import("./mocks/browser"); + const worker = await createWorker(); + return worker.start({ + onUnhandledRequest: "bypass", + quiet: true, + serviceWorker: { url: `${import.meta.env.BASE_URL}mockServiceWorker.js` }, + }); +} + +enableMocking().then(() => { + const rootElement = document.getElementById("app"); + if (rootElement && !rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement); + root.render( + + + + + + , + ); + } +}); diff --git a/src/uis/nvcf-ui/ui/src/mocks/browser.ts b/src/uis/nvcf-ui/ui/src/mocks/browser.ts new file mode 100644 index 000000000..359059ecd --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/browser.ts @@ -0,0 +1,24 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { setupWorker } from "msw/browser"; +import { getHandlers } from "./handlers"; + +export async function createWorker() { + const handlers = await getHandlers(); + return setupWorker(...handlers); +} diff --git a/src/uis/nvcf-ui/ui/src/mocks/handlers.ts b/src/uis/nvcf-ui/ui/src/mocks/handlers.ts new file mode 100644 index 000000000..608034495 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/handlers.ts @@ -0,0 +1,57 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { HttpHandler } from "msw"; +import { getStoreHandlers } from "./store"; + +/** + * All scenarios should be added to the `scenarios` directory -- each folder representing a feature, + * and each file representing a scenario for the given feature. + * + * To enable one or more scenarios, set the `VITE_SCENARIO` environment variable to a comma-separated list of `feature:file` values. + * For example: `VITE_SCENARIO=functions:empty-list,tasks:lauching-task` would enable the `empty-list` scenario for the `functions` feature. + */ + +const scenarioModules = import.meta.glob<{ handlers: HttpHandler[] }>( + "./scenarios/**/*.ts", +); + +/** + * Baseline handlers used by both the browser worker and the Node test server. + * Backed by the seeded store so data is stable and referentially consistent + * (list ids match detail, everything keyed by the seeded account ncaIds). + * `VITE_SCENARIO` overrides layer on top of these (see `getHandlers`). + */ +export const defaultHandlers: HttpHandler[] = getStoreHandlers(); + +export async function getHandlers(): Promise { + const scenarios = + import.meta.env.VITE_SCENARIO?.split(",").filter(Boolean) ?? []; + const overrides: HttpHandler[] = []; + + for (const name of scenarios) { + const [feature, file] = name.split(":"); + const loader = scenarioModules[`./scenarios/${feature}/${file}.ts`]; + if (!loader) { + console.warn(`Unknown scenario: ${name}`); + continue; + } + const { handlers } = await loader(); + overrides.push(...handlers); + } + return overrides.concat(defaultHandlers); +} diff --git a/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/all-statuses.ts b/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/all-statuses.ts new file mode 100644 index 000000000..3365af4c6 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/all-statuses.ts @@ -0,0 +1,42 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getGetAllFunctionsMockHandler, + getGetFunctionVersionResponseMock, +} from "~/generated/api/function-management/function-management.msw"; +import type { FunctionDto } from "~/generated/model/functionDto"; + +const STATUSES = [ + "ACTIVE", + "DEPLOYING", + "ERROR", + "INACTIVE", + "DELETED", + "DEGRADED", + "DEGRADING", +] as const; + +// One function per status — useful for verifying all status badge variants at once. +const FUNCTIONS: FunctionDto[] = STATUSES.map((status) => ({ + ...getGetFunctionVersionResponseMock().function, + status, +})); + +export const handlers = [ + getGetAllFunctionsMockHandler({ functions: FUNCTIONS }), +]; diff --git a/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/empty-list.ts b/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/empty-list.ts new file mode 100644 index 000000000..59e7c46b9 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/empty-list.ts @@ -0,0 +1,20 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getGetAllFunctionsMockHandler } from "~/generated/api/function-management/function-management.msw"; + +export const handlers = [getGetAllFunctionsMockHandler({ functions: [] })]; diff --git a/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/no-deployment.ts b/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/no-deployment.ts new file mode 100644 index 000000000..a8beeb63c --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/scenarios/functions/no-deployment.ts @@ -0,0 +1,31 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getGetFunctionDeploymentMockHandler, + getGetFunctionDeploymentResponseMock, +} from "~/generated/api/function-deployment/function-deployment.msw"; + +// Forces the "No Instance Type Yet" empty state in the Instance Types panel. +export const handlers = [ + getGetFunctionDeploymentMockHandler({ + deployment: { + ...getGetFunctionDeploymentResponseMock().deployment, + deploymentSpecifications: [], + }, + }), +]; diff --git a/src/uis/nvcf-ui/ui/src/mocks/server.ts b/src/uis/nvcf-ui/ui/src/mocks/server.ts new file mode 100644 index 000000000..e7416c59c --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/server.ts @@ -0,0 +1,21 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { setupServer } from "msw/node"; +import { defaultHandlers } from "~/mocks/handlers"; + +export const server = setupServer(...defaultHandlers); diff --git a/src/uis/nvcf-ui/ui/src/mocks/store/handlers.ts b/src/uis/nvcf-ui/ui/src/mocks/store/handlers.ts new file mode 100644 index 000000000..fcd5d305f --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/store/handlers.ts @@ -0,0 +1,93 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { HttpHandler } from "msw"; +import { getGetCloudAccountsMockHandler } from "~/generated/api/account/account.msw"; +import { + getGetClusterForNcaIdAndClusterIdMockHandler, + getGetClusterForNcaIdAndClusterIdResponseMock, + getGetClustersMockHandler, +} from "~/generated/api/clusters/clusters.msw"; +import { getGetControlPlaneStatusMockHandler } from "~/generated/api/control-plane/control-plane.msw"; +import { + getGetAllFunctionDeploymentsMockHandler, + getGetFunctionDeploymentMockHandler, + getGetFunctionDeploymentResponseMock, +} from "~/generated/api/function-deployment/function-deployment.msw"; +import { + getGetAllFunctionsMockHandler, + getGetFunctionVersionMockHandler, + getGetFunctionVersionResponseMock, +} from "~/generated/api/function-management/function-management.msw"; +import { mockStore } from "./store"; + +/** + * Store-backed default handlers. Each resolves the tenant bucket from + * `info.params.ncaId` (and detail routes from their id params), falling back to + * a freshly generated mock on a miss so an unknown id never 404s. + */ +export function getStoreHandlers(): HttpHandler[] { + return [ + // Accounts — the seeded list; drives which ncaId everything else keys on. + getGetCloudAccountsMockHandler({ cloudAccounts: mockStore.cloudAccounts }), + + // Functions + getGetAllFunctionsMockHandler((info) => { + const account = mockStore.account(info.params.ncaId as string); + return { functions: [...(account?.functions.values() ?? [])] }; + }), + getGetFunctionVersionMockHandler((info) => { + const account = mockStore.account(info.params.ncaId as string); + const key = `${info.params.functionId}/${info.params.functionVersionId}`; + const fn = account?.functions.get(key); + return fn ? { function: fn } : getGetFunctionVersionResponseMock(); + }), + + // Deployments + getGetAllFunctionDeploymentsMockHandler((info) => { + const account = mockStore.account(info.params.ncaId as string); + return { + deployments: [...(account?.deployments.values() ?? [])].map( + (d) => d.deployment, + ), + }; + }), + getGetFunctionDeploymentMockHandler((info) => { + const account = mockStore.account(info.params.ncaId as string); + return ( + account?.deployments.get(info.params.functionId as string) ?? + getGetFunctionDeploymentResponseMock() + ); + }), + + // Clusters + getGetClustersMockHandler((info) => { + const account = mockStore.account(info.params.ncaId as string); + return [...(account?.clusters.values() ?? [])]; + }), + getGetClusterForNcaIdAndClusterIdMockHandler((info) => { + const account = mockStore.account(info.params.ncaId as string); + return ( + account?.clusters.get(info.params.clusterId as string) ?? + getGetClusterForNcaIdAndClusterIdResponseMock() + ); + }), + + // Control plane — global. + getGetControlPlaneStatusMockHandler(mockStore.controlPlaneStatus), + ]; +} diff --git a/src/uis/nvcf-ui/ui/src/mocks/store/index.ts b/src/uis/nvcf-ui/ui/src/mocks/store/index.ts new file mode 100644 index 000000000..2b3e8ab6d --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/store/index.ts @@ -0,0 +1,19 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { getStoreHandlers } from "./handlers"; +export { mockStore } from "./store"; diff --git a/src/uis/nvcf-ui/ui/src/mocks/store/store.ts b/src/uis/nvcf-ui/ui/src/mocks/store/store.ts new file mode 100644 index 000000000..b185ac3a1 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/mocks/store/store.ts @@ -0,0 +1,113 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { faker } from "@faker-js/faker"; +import { getGetCloudAccountsResponseMock } from "~/generated/api/account/account.msw"; +import { getGetClustersResponseMock } from "~/generated/api/clusters/clusters.msw"; +import { getGetControlPlaneStatusResponseMock } from "~/generated/api/control-plane/control-plane.msw"; +import { getGetFunctionDeploymentResponseMock } from "~/generated/api/function-deployment/function-deployment.msw"; +import { getGetFunctionVersionResponseMock } from "~/generated/api/function-management/function-management.msw"; +import type { AccountDto } from "~/generated/model/accountDto"; +import type { DeploymentResponse } from "~/generated/model/deploymentResponse"; +import type { FunctionDto } from "~/generated/model/functionDto"; +import type { GetClusterResponse } from "~/generated/model/getClusterResponse"; + +/** + * A seeded, referentially-consistent in-memory dataset that backs the default + * mock handlers. Built once at module load — so unlike the raw generated mocks + * (which re-roll random data on every request), list and detail views stay + * stable and their IDs line up. Everything is keyed by account `ncaId`, which + * is what every account-scoped endpoint resolves against. + */ + +// Fixed seed so the dataset is reproducible across reloads, tests, and CI — +// same names/ids every run, which is what "seeded" implies. +faker.seed(1337); + +const ACCOUNT_COUNT = 3; +const FUNCTIONS_PER_ACCOUNT = 8; +const CLUSTERS_PER_ACCOUNT = 6; + +type AccountData = { + /** keyed by `${functionId}/${versionId}` */ + functions: Map; + /** keyed by functionId */ + deployments: Map; + /** keyed by clusterId */ + clusters: Map; +}; + +function buildAccountData(ncaId: string): AccountData { + const functions = new Map(); + const deployments = new Map(); + const clusters = new Map(); + + for (let i = 0; i < FUNCTIONS_PER_ACCOUNT; i++) { + const fn: FunctionDto = { + ...getGetFunctionVersionResponseMock().function, + ncaId, + }; + functions.set(`${fn.id}/${fn.versionId}`, fn); + // Deployment stamped with the owning function's identity so the two agree. + deployments.set(fn.id, { + deployment: { + ...getGetFunctionDeploymentResponseMock().deployment, + ncaId, + functionId: fn.id, + functionVersionId: fn.versionId, + functionName: fn.name, + functionStatus: fn.status, + }, + }); + } + + for (const cluster of getGetClustersResponseMock().slice( + 0, + CLUSTERS_PER_ACCOUNT, + )) { + const clusterId = cluster.clusterId ?? faker.string.uuid(); + clusters.set(clusterId, { ...cluster, clusterId, ncaId }); + } + + return { functions, deployments, clusters }; +} + +// Fixed account list with stable ncaIds; every other entity is seeded under one. +const cloudAccounts: AccountDto[] = Array.from( + { length: ACCOUNT_COUNT }, + () => { + const [account] = getGetCloudAccountsResponseMock().cloudAccounts ?? []; + return { + ...account, + ncaId: faker.string.alphanumeric({ length: 13 }), + name: faker.company.name(), + }; + }, +); + +const accountData = new Map( + cloudAccounts.map((a) => [a.ncaId, buildAccountData(a.ncaId)]), +); + +// Control-plane status is global (not account-scoped) — seed a single snapshot. +const controlPlaneStatus = getGetControlPlaneStatusResponseMock(); + +export const mockStore = { + cloudAccounts, + controlPlaneStatus, + account: (ncaId: string): AccountData | undefined => accountData.get(ncaId), +}; diff --git a/src/uis/nvcf-ui/ui/src/rootRoute.tsx b/src/uis/nvcf-ui/ui/src/rootRoute.tsx new file mode 100644 index 000000000..d15c50861 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/rootRoute.tsx @@ -0,0 +1,119 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AppBar, + HorizontalNav, + type HorizontalNavItem, + SegmentedControl, + Text, + type Theme, +} from "@nvidia/foundations-react-core"; +import type { QueryClient } from "@tanstack/react-query"; +import { + createRootRouteWithContext, + HeadContent, + Link, + type LinkProps, + Outlet, + useMatchRoute, +} from "@tanstack/react-router"; +import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; +import { MonitorCog, Moon, Sun } from "lucide-react"; +import { Suspense } from "react"; + +import { AccountSwitcher } from "./features/accounts/components/AccountSwitcher"; +import { AccountSwitcherSkeleton } from "./features/accounts/components/AccountSwitcherSkeleton"; +import { getGetCloudAccountsQueryOptions } from "./generated/api/account/account"; +import { useThemePreference } from "./hooks/useThemePreference"; + +type NavItem = HorizontalNavItem & { href: LinkProps["to"] }; + +const navItems: NavItem[] = [ + { value: "/", href: "/", children: "Dashboard" }, + { value: "/clusters", href: "/clusters", children: "Compute Clusters" }, + { value: "/functions", href: "/functions", children: "Functions" }, +]; + +function AppShell() { + const matchRoute = useMatchRoute(); + const { themePreference, setThemePreference } = useThemePreference(); + const activeValue = navItems.find((item) => + matchRoute({ to: item.href, fuzzy: item.value !== "/" }), + )?.value; + + return ( + <> + + + , + value: "system", + "aria-label": "System Theme", + }, + { + children: , + value: "light", + "aria-label": "Light Theme", + }, + { + children: , + value: "dark", + "aria-label": "Dark Theme", + }, + ]} + onValueChange={(value) => setThemePreference(value as Theme)} + size="small" + /> + }> + + + + } + slotStart={NVCF} + > + ( + + )} + value={activeValue} + /> + +
+ +
+ {import.meta.env.DEV && } + + ); +} + +export const rootRoute = createRootRouteWithContext<{ + queryClient: QueryClient; +}>()({ + component: AppShell, + head: () => ({ meta: [{ title: "NVCF" }] }), + beforeLoad: ({ context }) => { + void context.queryClient.prefetchQuery(getGetCloudAccountsQueryOptions()); + }, +}); diff --git a/src/uis/nvcf-ui/ui/src/router.tsx b/src/uis/nvcf-ui/ui/src/router.tsx new file mode 100644 index 000000000..56417ed55 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/router.tsx @@ -0,0 +1,71 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRouter } from "@tanstack/react-router"; +import { NotFound } from "./components/NotFound"; +import { RouteErrorFallback } from "./components/RouteErrorFallback"; +import { RouteSpinner } from "./components/RouteSpinner"; +import { + clusterDetailRoute, + clustersListRoute, +} from "./features/clusters/routes"; +import { dashboardRoute } from "./features/dashboard/routes"; +import { + functionDetailRoute, + functionsListRoute, +} from "./features/functions/routes"; +import { queryClient } from "./lib/queryClient"; +import { rootRoute } from "./rootRoute"; + +const routeTree = rootRoute.addChildren([ + clustersListRoute, + clusterDetailRoute, + dashboardRoute, + functionsListRoute, + functionDetailRoute, +]); + +export const router = createRouter({ + routeTree, + basepath: import.meta.env.BASE_URL, + defaultPreload: "intent", + defaultPendingMs: 300, + defaultPreloadStaleTime: 0, + defaultErrorComponent: RouteErrorFallback, + defaultNotFoundComponent: NotFound, + defaultPendingComponent: RouteSpinner, + scrollRestoration: true, + context: { queryClient }, +}); + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } + + interface StaticDataRouteOption { + account?: { + /** + * Where to send the user if they switch the active account while on + * this route. Set on account-specific pages (e.g. a function detail) + * that won't resolve under a different account; omit on pages that + * should just refresh in place. + */ + redirectOnChange?: string; + }; + } +} diff --git a/src/uis/nvcf-ui/ui/src/styles.css b/src/uis/nvcf-ui/ui/src/styles.css new file mode 100644 index 000000000..ec5d97b1b --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/styles.css @@ -0,0 +1,55 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* fonts, css variables, CSS reset/preflight */ +@import "../vendor/kui-foundations/base-external.css"; +/* component specific styling */ +@import "../vendor/kui-foundations/components.css"; +@import "@nvidia/foundations-tailwind-plugin"; +@source "../node_modules/@nvidia/foundations-react-core"; + +#style-root { + @tailwind utilities; +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + min-height: 100%; + background-color: var(--background-color-surface-sunken); +} + +body { + margin: 0; +} + +/* Keep the browser-default cross-fade for view transitions, tuned snappy. */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 180ms; +} + +@media (prefers-reduced-motion: reduce) { + ::view-transition-old(root), + ::view-transition-new(root) { + animation: none; + } +} diff --git a/src/uis/nvcf-ui/ui/src/testing/render.tsx b/src/uis/nvcf-ui/ui/src/testing/render.tsx new file mode 100644 index 000000000..50c72b59d --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/testing/render.tsx @@ -0,0 +1,74 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import type { AnyRoute } from "@tanstack/react-router"; +import { + createMemoryHistory, + createRouter, + RouterProvider, +} from "@tanstack/react-router"; +import { type RenderOptions, render } from "@testing-library/react"; +import { NotFound } from "~/components/NotFound"; +import { RouteErrorFallback } from "~/components/RouteErrorFallback"; +import { RouteSpinner } from "~/components/RouteSpinner"; +import { rootRoute } from "~/rootRoute"; + +function createTestQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); +} + +interface RenderWithRouterOptions extends Omit { + initialLocation?: string; + routes?: AnyRoute[]; +} + +export function renderWithRouter({ + initialLocation = "/", + routes = [], + ...renderOptions +}: RenderWithRouterOptions = {}) { + if (routes.length === 0) { + throw new Error("At least one route is required."); + } + + const queryClient = createTestQueryClient(); + const routeTree = rootRoute.addChildren(routes); + const router = createRouter({ + routeTree, + history: createMemoryHistory({ initialEntries: [initialLocation] }), + context: { queryClient }, + defaultErrorComponent: RouteErrorFallback, + defaultNotFoundComponent: NotFound, + defaultPendingComponent: RouteSpinner, + }); + + return { + ...render( + + + , + renderOptions, + ), + router, + queryClient, + }; +} diff --git a/src/uis/nvcf-ui/ui/src/types/table.d.ts b/src/uis/nvcf-ui/ui/src/types/table.d.ts new file mode 100644 index 000000000..8c96b352a --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/types/table.d.ts @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import "@tanstack/react-table"; + +declare module "@tanstack/react-table" { + interface ColumnMeta { + filterVariant?: "single-select" | "multi-select"; + filterOptions?: { label: string; value: string }[]; + sortType?: "text" | "date" | "number"; + sortLabels?: { asc: string; desc: string }; + } + + interface FilterFns { + includesValue: FilterFn; + multiSelect: FilterFn; + } +} diff --git a/src/uis/nvcf-ui/ui/src/utils/clipboard.test.ts b/src/uis/nvcf-ui/ui/src/utils/clipboard.test.ts new file mode 100644 index 000000000..3aed26440 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/utils/clipboard.test.ts @@ -0,0 +1,46 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { afterEach, describe, expect, it, vi } from "vitest"; +import { copyToClipboard } from "./clipboard"; + +describe("copyToClipboard", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("writes via navigator.clipboard in a secure context", async () => { + await copyToClipboard("my-function-id"); + + expect(await navigator.clipboard.readText()).toBe("my-function-id"); + }); + + it("falls back to execCommand when navigator.clipboard rejects", async () => { + // Simulate an insecure (HTTP) context where writeText is unavailable. + vi.spyOn(navigator.clipboard, "writeText").mockRejectedValue( + new Error("insecure context"), + ); + const execCommand = vi.fn(() => true); + document.execCommand = execCommand; + + await copyToClipboard("my-function-id"); + + expect(execCommand).toHaveBeenCalledWith("copy"); + // The temporary textarea is cleaned up after copying. + expect(document.querySelector("textarea")).toBeNull(); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/utils/clipboard.ts b/src/uis/nvcf-ui/ui/src/utils/clipboard.ts new file mode 100644 index 000000000..61ef3c7fe --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/utils/clipboard.ts @@ -0,0 +1,43 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Copy text to the clipboard. + * + * `navigator.clipboard` is only exposed in secure contexts (HTTPS or + * localhost). When the app is served over plain HTTP it is `undefined`, so we + * fall back to a hidden textarea + `document.execCommand("copy")`, which still + * works in insecure contexts. + */ +export async function copyToClipboard(text: string): Promise { + try { + await navigator.clipboard.writeText(text); + } catch { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", ""); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand("copy"); + } finally { + document.body.removeChild(textarea); + } + } +} diff --git a/src/uis/nvcf-ui/ui/src/utils/formatters.test.ts b/src/uis/nvcf-ui/ui/src/utils/formatters.test.ts new file mode 100644 index 000000000..153bdbcd5 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/utils/formatters.test.ts @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from "vitest"; +import { toTitleCase } from "./formatters"; + +describe("toTitleCase", () => { + it.each([ + ["ACTIVE", "Active"], + ["HELM_FUNCTION", "Helm Function"], + ])("%s → %s", (input, expected) => { + expect(toTitleCase(input)).toBe(expected); + }); +}); diff --git a/src/uis/nvcf-ui/ui/src/utils/formatters.ts b/src/uis/nvcf-ui/ui/src/utils/formatters.ts new file mode 100644 index 000000000..c0605ae5b --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/utils/formatters.ts @@ -0,0 +1,47 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dayjs from "dayjs"; +import { startCase, toLower } from "lodash-es"; + +// Strings + +export function toTitleCase(value: string): string { + return startCase(toLower(value)); +} + +// Numbers + +export function formatNumberCompact(value: number): string { + return new Intl.NumberFormat("en-US", { + notation: "compact", + compactDisplay: "short", + }).format(value); +} + +// Dates + +const DATE_TIME_FORMAT = "MM/DD/YYYY hh:mm A"; + +/** + * Formats an ISO timestamp (e.g. "07/14/2026 03:45 PM"), returning "—" when the + * value is absent. Where a missing value should render nothing instead of a + * dash, guard the call site rather than relying on this fallback. + */ +export function formatDateTime(value: string | null | undefined): string { + return value ? dayjs(value).format(DATE_TIME_FORMAT) : "—"; +} diff --git a/src/uis/nvcf-ui/ui/src/utils/resize-observer.ts b/src/uis/nvcf-ui/ui/src/utils/resize-observer.ts new file mode 100644 index 000000000..0ae6c5564 --- /dev/null +++ b/src/uis/nvcf-ui/ui/src/utils/resize-observer.ts @@ -0,0 +1,59 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* v8 ignore start */ + +const callbacks = new Map void>(); +const pending = new Set(); +let rafId: number | null = null; + +function scheduleUpdate(): void { + if (rafId !== null) return; + + rafId = requestAnimationFrame(() => { + for (const element of pending) { + callbacks.get(element)?.(); + } + pending.clear(); + rafId = null; + }); +} + +const observer = + typeof window !== "undefined" && "ResizeObserver" in window + ? new ResizeObserver((entries) => { + for (const entry of entries) { + pending.add(entry.target); + } + scheduleUpdate(); + }) + : null; + +export function observe(element: Element, callback: () => void): void { + if (!observer) return; + callbacks.set(element, callback); + observer.observe(element); +} + +export function unobserve(element: Element): void { + if (!observer) return; + callbacks.delete(element); + observer.unobserve(element); + pending.delete(element); +} + +/* v8 ignore end */ diff --git a/src/uis/nvcf-ui/ui/tsconfig.json b/src/uis/nvcf-ui/ui/tsconfig.json new file mode 100644 index 000000000..0320e6941 --- /dev/null +++ b/src/uis/nvcf-ui/ui/tsconfig.json @@ -0,0 +1,26 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "target": "ES2022", + "jsx": "react-jsx", + "module": "ESNext", + "paths": { + "~/*": ["./src/*"] + }, + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noUncheckedSideEffectImports": true, + "noUnusedLocals": true, + "noUnusedParameters": true + } +} diff --git a/src/uis/nvcf-ui/ui/vendor/kui-foundations/.version b/src/uis/nvcf-ui/ui/vendor/kui-foundations/.version new file mode 100644 index 000000000..9dbb0c005 --- /dev/null +++ b/src/uis/nvcf-ui/ui/vendor/kui-foundations/.version @@ -0,0 +1 @@ +1.7.0 \ No newline at end of file diff --git a/src/uis/nvcf-ui/ui/vendor/kui-foundations/base-external.css b/src/uis/nvcf-ui/ui/vendor/kui-foundations/base-external.css new file mode 100644 index 000000000..6f54d573f --- /dev/null +++ b/src/uis/nvcf-ui/ui/vendor/kui-foundations/base-external.css @@ -0,0 +1,21 @@ +/** +* Lucide Icons CSS (Open Source Alternative) +* Maps NVIDIA icon names to Lucide equivalents +* +* ISC License +* +* Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--font-sans,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");-webkit-tap-highlight-color:transparent}body{-webkit-font-smoothing:antialiased}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--font-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:var(--text-color-placeholder)}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:var(--text-color-placeholder,currentcolor)}@supports (color:color-mix(in lab, red, red)){::placeholder{color:var(--text-color-placeholder,color-mix(in oklab,currentcolor 50.0%,transparent))}}}textarea{resize:vertical}textarea,input{font:inherit;letter-spacing:inherit;word-spacing:inherit}::-webkit-search-decoration{-webkit-appearance:none;display:none}::-webkit-search-cancel-button{-webkit-appearance:none;display:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:where(.nv-modal-overlay[popover],.nv-side-panel-overlay[popover],.nv-popover-content[popover],.nv-tooltip-content[popover],.nv-dropdown-content[popover],.nv-dropdown-sub-content[popover],.nv-select-content[popover],.nv-combobox-content[popover],dialog.nv-modal-overlay,dialog.nv-modal-dialog,dialog.nv-side-panel-overlay,dialog.nv-side-panel-dialog){box-sizing:border-box;border-collapse:separate;border-spacing:0;caption-side:top;color:var(--text-color-primary);cursor:auto;direction:initial;empty-cells:show;font-family:var(--font-sans);font-feature-settings:normal;font-kerning:auto;font-size:var(--text-14);font-variation-settings:normal;font-style:normal;font-stretch:100%;font-weight:var(--font-weight-regular);hyphens:manual;letter-spacing:normal;line-height:var(--leading-lh-150);overflow-wrap:normal;tab-size:4;text-align:start;text-indent:0;text-orientation:mixed;text-shadow:none;text-transform:none;text-wrap:wrap;unicode-bidi:initial;visibility:visible;white-space:normal;word-break:normal;word-spacing:normal;writing-mode:horizontal-tb;text-decoration:none;list-style:disc}@property --nv-anchor-self{syntax:"*";inherits:false}@property --nv-anchor-tooltip{syntax:"*";inherits:false}@supports (interpolate-size:allow-keywords){:root{interpolate-size:allow-keywords}}.nv-motion-disabled [class*=nv-]:not([class*=nv-spinner-root]){transition-duration:0s!important;transition-delay:0s!important;animation-duration:0s!important;animation-delay:0s!important}@keyframes pulse{50%{opacity:.5}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes modal-in{0%{opacity:0}to{opacity:1}}@keyframes modal-out{0%{opacity:1}to{opacity:0}}@keyframes accordion-out{0%{height:0;padding-block:0;overflow:hidden}99%{overflow:hidden}to{height:var(--nv-accordion-content-height,var(--radix-accordion-content-height,var(--radix-collapsible-content-height)));overflow:visible}}@keyframes accordion-in{0%{height:var(--nv-accordion-content-height,var(--radix-accordion-content-height,var(--radix-collapsible-content-height)));overflow:hidden}to{height:0;padding-block:0;overflow:hidden}}@keyframes nv-popover-in{0%{opacity:0;translate:var(--nv-popover-translate-start)}to{opacity:1;translate:0}}@keyframes nv-tooltip-in{0%{opacity:0;translate:var(--nv-tooltip-translate-start)}to{opacity:1;translate:0}}@font-face{font-family:NVIDIA Sans Fallback;font-style:normal;font-weight:300;ascent-override:93.59%;descent-override:26.74%;line-gap-override:0%;size-adjust:104.71%;src:local(Arial),local(Inter),local(Helvetica),local(DejaVu Sans),local(Liberation Sans),local(Noto Sans),local(Ubuntu),local(FreeSans),local("sans-serif")}@font-face{font-family:NVIDIA Sans Fallback;font-style:italic;font-weight:300;ascent-override:97.18%;descent-override:27.77%;line-gap-override:0%;size-adjust:100.84%;src:local(Arial Italic),local(Inter Italic),local(Helvetica Oblique),local(DejaVu Sans Oblique),local(Liberation Sans Italic),local(Noto Sans Italic),local(Ubuntu Italic),local(FreeSans Oblique),local("sans-serif")}@font-face{font-family:NVIDIA Sans Fallback;font-style:normal;font-weight:400;ascent-override:92.7%;descent-override:26.49%;line-gap-override:0%;size-adjust:105.71%;src:local(Arial),local(Inter),local(Helvetica),local(DejaVu Sans),local(Liberation Sans),local(Noto Sans),local(Ubuntu),local(FreeSans),local("sans-serif")}@font-face{font-family:NVIDIA Sans Fallback;font-style:italic;font-weight:400;ascent-override:96.18%;descent-override:27.48%;line-gap-override:0%;size-adjust:101.89%;src:local(Arial Italic),local(Inter Italic),local(Helvetica Oblique),local(DejaVu Sans Oblique),local(Liberation Sans Italic),local(Noto Sans Italic),local(Ubuntu Italic),local(FreeSans Oblique),local("sans-serif")}@font-face{font-family:NVIDIA Sans Fallback;font-style:normal;font-weight:500;ascent-override:91.53%;descent-override:26.15%;line-gap-override:0%;size-adjust:107.07%;src:local(Arial),local(Inter),local(Helvetica),local(DejaVu Sans),local(Liberation Sans),local(Noto Sans),local(Ubuntu),local(FreeSans),local("sans-serif")}@font-face{font-family:NVIDIA Sans Fallback;font-style:italic;font-weight:500;ascent-override:94.81%;descent-override:27.09%;line-gap-override:0%;size-adjust:103.36%;src:local(Arial Italic),local(Inter Italic),local(Helvetica Oblique),local(DejaVu Sans Oblique),local(Liberation Sans Italic),local(Noto Sans Italic),local(Ubuntu Italic),local(FreeSans Oblique),local("sans-serif")}@font-face{font-family:NVIDIA Sans Fallback;font-style:normal;font-weight:700;ascent-override:97.74%;descent-override:27.93%;line-gap-override:0%;size-adjust:100.26%;src:local(Arial Bold),local(Inter Bold),local(Helvetica Bold),local(DejaVu Sans Bold),local(Liberation Sans Bold),local(Noto Sans Bold),local(Ubuntu Bold),local(FreeSans Bold),local("sans-serif")}@font-face{font-family:NVIDIA Sans Fallback;font-style:italic;font-weight:700;ascent-override:101.13%;descent-override:28.9%;line-gap-override:0%;size-adjust:96.9%;src:local(Arial Bold Italic),local(Inter Bold Italic),local(Helvetica Bold Oblique),local(DejaVu Sans Bold Oblique),local(Liberation Sans Bold Italic),local(Noto Sans Bold Italic),local(Ubuntu Bold Italic),local(FreeSans Bold Oblique),local("sans-serif")}@font-face{font-family:JetBrains Mono;font-style:normal;font-weight:300;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-Light.woff2)format("woff2")}@font-face{font-family:JetBrains Mono;font-style:italic;font-weight:300;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-LightItalic.woff2)format("woff2")}@font-face{font-family:JetBrains Mono;font-style:normal;font-weight:400;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-Regular.woff2)format("woff2")}@font-face{font-family:JetBrains Mono;font-style:italic;font-weight:400;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-Italic.woff2)format("woff2")}@font-face{font-family:JetBrains Mono;font-style:normal;font-weight:500;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-Medium.woff2)format("woff2")}@font-face{font-family:JetBrains Mono;font-style:italic;font-weight:500;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-MediumItalic.woff2)format("woff2")}@font-face{font-family:JetBrains Mono;font-style:normal;font-weight:700;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-Bold.woff2)format("woff2")}@font-face{font-family:JetBrains Mono;font-style:italic;font-weight:700;font-display:swap;src:url(https://brand-assets.cne.ngc.nvidia.com/assets/fonts/jetbrains-mono/1.0.0/JetBrainsMono-BoldItalic.woff2)format("woff2")}@font-face{font-family:JetBrains Mono Fallback;font-style:normal;font-weight:300;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New)}@font-face{font-family:JetBrains Mono Fallback;font-style:italic;font-weight:300;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New Italic)}@font-face{font-family:JetBrains Mono Fallback;font-style:normal;font-weight:400;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New)}@font-face{font-family:JetBrains Mono Fallback;font-style:italic;font-weight:400;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New Italic)}@font-face{font-family:JetBrains Mono Fallback;font-style:normal;font-weight:500;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New)}@font-face{font-family:JetBrains Mono Fallback;font-style:italic;font-weight:500;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New Italic)}@font-face{font-family:JetBrains Mono Fallback;font-style:normal;font-weight:700;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New Bold)}@font-face{font-family:JetBrains Mono Fallback;font-style:italic;font-weight:700;ascent-override:102.02%;descent-override:30%;line-gap-override:0%;size-adjust:99.98%;src:local(Courier New Bold Italic)}@font-face{font-family:missing-ligature-font;src:local(Arial),local(Verdana),local(Tahoma),local(Trebuchet MS);size-adjust:0%}:where(.nv-icon){width:1em;height:1em;display:block}.nv-icon:before{content:"";background-color:currentColor;width:100%;height:100%;display:block}.nv-icon-bell:before{-webkit-mask:var(--nv-icon-bell)no-repeat center;mask:var(--nv-icon-bell)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-calendar:before{-webkit-mask:var(--nv-icon-calendar)no-repeat center;mask:var(--nv-icon-calendar)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-check:before{-webkit-mask:var(--nv-icon-check)no-repeat center;mask:var(--nv-icon-check)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-check-circle:before{-webkit-mask:var(--nv-icon-check-circle)no-repeat center;mask:var(--nv-icon-check-circle)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-clock:before{-webkit-mask:var(--nv-icon-clock)no-repeat center;mask:var(--nv-icon-clock)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-chevron-double-left:before{-webkit-mask:var(--nv-icon-chevron-double-left)no-repeat center;mask:var(--nv-icon-chevron-double-left)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-chevron-double-right:before{-webkit-mask:var(--nv-icon-chevron-double-right)no-repeat center;mask:var(--nv-icon-chevron-double-right)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-chevron-down:before{-webkit-mask:var(--nv-icon-chevron-down)no-repeat center;mask:var(--nv-icon-chevron-down)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-chevron-left:before{-webkit-mask:var(--nv-icon-chevron-left)no-repeat center;mask:var(--nv-icon-chevron-left)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-chevron-right:before{-webkit-mask:var(--nv-icon-chevron-right)no-repeat center;mask:var(--nv-icon-chevron-right)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-chevron-up:before{-webkit-mask:var(--nv-icon-chevron-up)no-repeat center;mask:var(--nv-icon-chevron-up)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-circle-tick:before{-webkit-mask:var(--nv-icon-circle-tick)no-repeat center;mask:var(--nv-icon-circle-tick)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-close:before{-webkit-mask:var(--nv-icon-close)no-repeat center;mask:var(--nv-icon-close)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-error:before{-webkit-mask:var(--nv-icon-error)no-repeat center;mask:var(--nv-icon-error)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-filter:before{-webkit-mask:var(--nv-icon-filter)no-repeat center;mask:var(--nv-icon-filter)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-info-circle:before{-webkit-mask:var(--nv-icon-info-circle)no-repeat center;mask:var(--nv-icon-info-circle)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-menu:before{-webkit-mask:var(--nv-icon-menu)no-repeat center;mask:var(--nv-icon-menu)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-warning:before{-webkit-mask:var(--nv-icon-warning)no-repeat center;mask:var(--nv-icon-warning)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-retry:before{-webkit-mask:var(--nv-icon-retry)no-repeat center;mask:var(--nv-icon-retry)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-document:before{-webkit-mask:var(--nv-icon-document)no-repeat center;mask:var(--nv-icon-document)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-trash:before{-webkit-mask:var(--nv-icon-trash)no-repeat center;mask:var(--nv-icon-trash)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon-copy-doc:before{-webkit-mask:var(--nv-icon-copy-doc)no-repeat center;mask:var(--nv-icon-copy-doc)no-repeat center;-webkit-mask-size:contain;mask-size:contain}.nv-icon{--nv-icon-bell:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M10.268 21a2 2 0 0 0 3.464 0' /%3e %3cpath d='M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326' /%3e %3c/svg%3e");--nv-icon-calendar:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M8 2v4' /%3e %3cpath d='M16 2v4' /%3e %3crect width='18' height='18' x='3' y='4' rx='2' /%3e %3cpath d='M3 10h18' /%3e %3c/svg%3e");--nv-icon-check:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M20 6 9 17l-5-5' /%3e %3c/svg%3e");--nv-icon-check-circle:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M21.801 10A10 10 0 1 1 17 3.335' /%3e %3cpath d='m9 11 3 3L22 4' /%3e %3c/svg%3e");--nv-icon-clock:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M12 6v6l4 2' /%3e %3ccircle cx='12' cy='12' r='10' /%3e %3c/svg%3e");--nv-icon-chevron-double-left:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='m11 17-5-5 5-5' /%3e %3cpath d='m18 17-5-5 5-5' /%3e %3c/svg%3e");--nv-icon-chevron-double-right:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='m6 17 5-5-5-5' /%3e %3cpath d='m13 17 5-5-5-5' /%3e %3c/svg%3e");--nv-icon-chevron-down:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='m6 9 6 6 6-6' /%3e %3c/svg%3e");--nv-icon-chevron-left:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='m15 18-6-6 6-6' /%3e %3c/svg%3e");--nv-icon-chevron-right:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='m9 18 6-6-6-6' /%3e %3c/svg%3e");--nv-icon-chevron-up:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='m18 15-6-6-6 6' /%3e %3c/svg%3e");--nv-icon-circle-tick:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3ccircle cx='12' cy='12' r='10' /%3e %3cpath d='m9 12 2 2 4-4' /%3e %3c/svg%3e");--nv-icon-close:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M18 6 6 18' /%3e %3cpath d='m6 6 12 12' /%3e %3c/svg%3e");--nv-icon-error:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3ccircle cx='12' cy='12' r='10' /%3e %3cline x1='12' x2='12' y1='8' y2='12' /%3e %3cline x1='12' x2='12.01' y1='16' y2='16' /%3e %3c/svg%3e");--nv-icon-filter:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z' /%3e %3c/svg%3e");--nv-icon-info-circle:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3ccircle cx='12' cy='12' r='10' /%3e %3cpath d='M12 16v-4' /%3e %3cpath d='M12 8h.01' /%3e %3c/svg%3e");--nv-icon-menu:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M4 12h16' /%3e %3cpath d='M4 18h16' /%3e %3cpath d='M4 6h16' /%3e %3c/svg%3e");--nv-icon-warning:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3' /%3e %3cpath d='M12 9v4' /%3e %3cpath d='M12 17h.01' /%3e %3c/svg%3e");--nv-icon-retry:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' /%3e %3cpath d='M3 3v5h5' /%3e %3c/svg%3e");--nv-icon-document:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z' /%3e %3cpath d='M14 2v4a2 2 0 0 0 2 2h4' /%3e %3c/svg%3e");--nv-icon-trash:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3cpath d='M10 11v6' /%3e %3cpath d='M14 11v6' /%3e %3cpath d='M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6' /%3e %3cpath d='M3 6h18' /%3e %3cpath d='M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2' /%3e %3c/svg%3e");--nv-icon-copy-doc:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3e %3crect width='14' height='14' x='8' y='8' rx='2' ry='2' /%3e %3cpath d='M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' /%3e %3c/svg%3e")}:root{--spacing:4px;--color-black:#000;--color-white:#fff;--color-brand:#76b900;--color-red-050:#ffe9e9;--color-red-100:#ffd7d7;--color-red-200:#fbb;--color-red-300:#ff8181;--color-red-400:#fe3f3f;--color-red-500:#e52020;--color-red-600:#c21e1e;--color-red-700:#961515;--color-red-800:#650b0b;--color-red-900:#4b0404;--color-red-950:#2d0100;--color-yellow-050:#feeeb2;--color-yellow-100:#fcde7b;--color-yellow-200:#f9c500;--color-yellow-300:#ef9100;--color-yellow-400:#df6500;--color-yellow-500:#d73d00;--color-yellow-600:#b93100;--color-yellow-700:#8d2600;--color-yellow-800:#601600;--color-yellow-900:#441000;--color-yellow-950:#2d0b00;--color-green-025:#dafb7d;--color-green-050:#cfff40;--color-green-100:#bff230;--color-green-200:#a5de15;--color-green-300:#76b900;--color-green-400:#549a00;--color-green-500:#3f8500;--color-green-600:#327100;--color-green-700:#265600;--color-green-800:#193800;--color-green-900:#142700;--color-green-950:#0d1a00;--color-teal-050:#adfcf8;--color-teal-100:#9aefe5;--color-teal-200:#3ae3c9;--color-teal-300:#1dbba4;--color-teal-400:#139a86;--color-teal-500:#0d8473;--color-teal-600:#097061;--color-teal-700:#04554b;--color-teal-800:#033831;--color-teal-900:#022723;--color-teal-950:#011a19;--color-blue-050:#cbf5ff;--color-blue-100:#ace;--color-blue-200:#7cd7fe;--color-blue-300:#10b1fb;--color-blue-400:#008af9;--color-blue-500:#0074df;--color-blue-600:#0060c7;--color-blue-700:#0046a4;--color-blue-800:#002781;--color-blue-900:#002050;--color-blue-950:#00112c;--color-purple-050:#fae9ff;--color-purple-100:#f9d4ff;--color-purple-200:#f0b9fd;--color-purple-300:#cd8ef0;--color-purple-400:#c359ef;--color-purple-500:#a846db;--color-purple-600:#952fc6;--color-purple-700:#741d9d;--color-purple-800:#4d1368;--color-purple-900:#331344;--color-purple-950:#1f0f27;--color-fuchsia-050:#ffe8f9;--color-fuchsia-100:#ffd3f2;--color-fuchsia-200:#feb5ee;--color-fuchsia-300:#fc79ca;--color-fuchsia-400:#e050b7;--color-fuchsia-500:#d2308e;--color-fuchsia-600:#b62475;--color-fuchsia-700:#8c1c55;--color-fuchsia-800:#5d1337;--color-fuchsia-900:#420d25;--color-fuchsia-950:#2d0919;--color-gray-000:#fff;--color-gray-025:#f7f7f7;--color-gray-050:#eee;--color-gray-100:#e0e0e0;--color-gray-1000:#000;--color-gray-200:#ccc;--color-gray-300:#a7a7a7;--color-gray-400:#898989;--color-gray-500:#757575;--color-gray-600:#636363;--color-gray-700:#4b4b4b;--color-gray-800:#313131;--color-gray-900:#222;--color-gray-950:#161616;--color-gray-975:#0c0c0c;--color-translucent-black-000:#0000;--color-translucent-black-050:#0000000d;--color-translucent-black-100:#0000001a;--color-translucent-black-120:#0000001f;--color-translucent-black-150:#00000026;--color-translucent-black-200:#0003;--color-translucent-black-300:#0000004d;--color-translucent-black-400:#0006;--color-translucent-black-500:#00000080;--color-translucent-black-600:#0009;--color-translucent-black-700:#000000b2;--color-translucent-black-800:#000c;--color-translucent-black-900:#000000e5;--color-translucent-white-000:#fff0;--color-translucent-white-050:#ffffff0d;--color-translucent-white-100:#ffffff1a;--color-translucent-white-120:#ffffff1f;--color-translucent-white-200:#fff3;--color-translucent-white-250:#ffffff40;--color-translucent-white-300:#ffffff4d;--color-translucent-white-400:#fff6;--color-translucent-white-500:#ffffff80;--color-translucent-white-600:#fff9;--color-translucent-white-700:#ffffffb2;--color-translucent-white-800:#fffc;--color-translucent-white-900:#ffffffe5;--font-sans:NVIDIA Sans,NVIDIA Sans Fallback,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:JetBrains Mono,JetBrains Mono Fallback,ui-monospace,monospace,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--text-10:.625rem;--text-12:.75rem;--text-14:.875rem;--text-16:1rem;--text-18:1.125rem;--text-20:1.25rem;--text-22:1.375rem;--text-24:1.5rem;--text-28:1.75rem;--text-32:2rem;--text-36:2.25rem;--text-40:2.5rem;--text-44:2.75rem;--text-48:3rem;--text-50:3.125rem;--text-56:3.5rem;--text-60:3.75rem;--text-64:4rem;--text-72:4.5rem;--text-80:5rem;--font-weight-light:300;--font-weight-regular:400;--font-weight-semibold:500;--font-weight-bold:700;--leading-lh-100:1;--leading-lh-125:1.25;--leading-lh-150:1.5;--leading-lh-175:1.75;--breakpoint-xs:20rem;--breakpoint-sm:36rem;--breakpoint-md:48rem;--breakpoint-lg:62rem;--breakpoint-xl:75rem;--breakpoint-2xl:100rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--border-width-none:0px;--border-width-1:1px;--border-width-2:2px;--border-width-3:3px;--border-width-4:4px;--radius-none:0px;--radius-sm:2px;--radius-md:4px;--radius-lg:8px;--radius-xl:16px;--radius-2xl:24px;--radius-3xl:32px;--radius-round:9999px;--shadow-lg:0px 8px 12px 0px var(--color-translucent-black-150);--shadow-md:0px 4px 6px 0px var(--color-translucent-black-120);--shadow-sm:0px 2px 4px 0px var(--color-translucent-black-120);--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-spin:spin 1s linear infinite;--transition-duration-150:.15s;--transition-duration-200:.2s;--transition-duration-250:.25s;--transition-duration-300:.3s;--ease-out:cubic-bezier(.4,0,.2,1);--radius-density-xl:var(--radius-xl);--spacing-density-xxs:2px;--spacing-density-xs:4px;--spacing-density-sm:6px;--spacing-density-md:8px;--spacing-density-lg:12px;--spacing-density-xl:16px;--spacing-density-2xl:24px;--spacing-density-3xl:32px;--spacing-density-4xl:48px;--spacing-density-5xl:64px;--nv-app-bar-height:48px;--nv-scrollbar-color:var(--color-gray-400)transparent}:where(:root),.nv-light,.light{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--background-color-accent-blue:var(--color-blue-050);--background-color-accent-blue-hover:var(--color-blue-050);--background-color-accent-blue-selected:var(--color-blue-200);--background-color-accent-blue-strong:var(--color-blue-700);--background-color-accent-blue-subtle:var(--color-blue-050);--background-color-accent-blue-subtle-hover:var(--color-blue-200);--background-color-accent-blue-subtle-selected:var(--color-blue-600);--background-color-accent-gray:var(--color-gray-200);--background-color-accent-gray-hover:var(--color-gray-300);--background-color-accent-gray-selected:var(--color-gray-700);--background-color-accent-gray-strong:var(--color-gray-700);--background-color-accent-gray-subtle:var(--color-gray-100);--background-color-accent-gray-subtle-hover:var(--color-gray-300);--background-color-accent-gray-subtle-selected:var(--color-gray-700);--background-color-accent-green:var(--color-green-025);--background-color-accent-green-hover:var(--color-green-025);--background-color-accent-green-selected:var(--color-green-050);--background-color-accent-green-strong:var(--color-green-800);--background-color-accent-green-subtle:var(--color-green-100);--background-color-accent-green-subtle-hover:var(--color-green-200);--background-color-accent-green-subtle-selected:var(--color-green-600);--background-color-accent-purple:var(--color-purple-200);--background-color-accent-purple-hover:var(--color-purple-050);--background-color-accent-purple-selected:var(--color-purple-100);--background-color-accent-purple-strong:var(--color-purple-700);--background-color-accent-purple-subtle:var(--color-purple-050);--background-color-accent-purple-subtle-hover:var(--color-purple-100);--background-color-accent-purple-subtle-selected:var(--color-purple-600);--background-color-accent-red:var(--color-red-100);--background-color-accent-red-hover:var(--color-red-050);--background-color-accent-red-selected:var(--color-red-100);--background-color-accent-red-strong:var(--color-red-700);--background-color-accent-red-subtle:var(--color-red-100);--background-color-accent-red-subtle-hover:var(--color-red-200);--background-color-accent-red-subtle-selected:var(--color-red-600);--background-color-accent-teal:var(--color-teal-100);--background-color-accent-teal-hover:var(--color-teal-050);--background-color-accent-teal-selected:var(--color-teal-100);--background-color-accent-teal-strong:var(--color-teal-800);--background-color-accent-teal-subtle:var(--color-teal-100);--background-color-accent-teal-subtle-hover:var(--color-teal-200);--background-color-accent-teal-subtle-selected:var(--color-teal-600);--background-color-accent-yellow:var(--color-yellow-100);--background-color-accent-yellow-hover:var(--color-yellow-050);--background-color-accent-yellow-selected:var(--color-yellow-100);--background-color-accent-yellow-strong:var(--color-yellow-700);--background-color-accent-yellow-subtle:var(--color-yellow-100);--background-color-accent-yellow-subtle-hover:var(--color-yellow-200);--background-color-accent-yellow-subtle-selected:var(--color-yellow-600);--background-color-background-contrast:var(--color-gray-900);--background-color-background-default:var(--color-gray-000);--background-color-background-emphasis:var(--color-gray-050);--background-color-background-highlight:var(--color-gray-100);--background-color-background-subtle:var(--color-gray-025);--background-color-component-skeleton:var(--color-gray-100);--background-color-component-skeleton-subtle:var(--color-gray-200);--background-color-component-tooltip:var(--color-gray-800);--background-color-component-track:var(--color-translucent-black-100);--background-color-component-track-inverse:var(--color-translucent-black-100);--background-color-feedback-danger:var(--color-red-300);--background-color-feedback-danger-hover:var(--color-red-600);--background-color-feedback-danger-pressed:var(--color-red-700);--background-color-feedback-danger-strong:var(--color-red-500);--background-color-feedback-danger-subtle-hover:var(--color-red-100);--background-color-feedback-danger-subtle-pressed:var(--color-red-200);--background-color-feedback-info:var(--color-blue-050);--background-color-feedback-success:var(--color-green-025);--background-color-feedback-warning:var(--color-yellow-100);--background-color-interaction-base:var(--color-gray-000);--background-color-interaction-disabled:var(--color-translucent-black-050);--background-color-interaction-disabled-checked:var(--color-translucent-black-400);--background-color-interaction-hover:var(--color-translucent-black-050);--background-color-interaction-inverse:var(--color-gray-900);--background-color-interaction-inverse-hover:var(--color-gray-950);--background-color-interaction-inverse-pressed:var(--color-gray-975);--background-color-interaction-pressed:var(--color-translucent-black-100);--background-color-interaction-primary-base:var(--color-green-300);--background-color-interaction-primary-hover:var(--color-green-400);--background-color-interaction-primary-selected:var(--color-green-500);--background-color-interaction-selected:var(--color-gray-000);--background-color-surface-base:var(--color-gray-000);--background-color-surface-blanket:var(--color-translucent-black-700);--background-color-surface-glass:var(--color-translucent-white-800);--background-color-surface-navigation:var(--color-gray-000);--background-color-surface-overlay:var(--color-gray-000);--background-color-surface-raised:var(--color-gray-000);--background-color-surface-sunken:var(--color-gray-025);--border-color-accent-black:var(--color-gray-1000);--border-color-accent-blue:var(--color-blue-600);--border-color-accent-gray:var(--color-gray-700);--border-color-accent-green:var(--color-green-700);--border-color-accent-purple:var(--color-purple-600);--border-color-accent-red:var(--color-red-600);--border-color-accent-teal:var(--color-teal-600);--border-color-accent-white:var(--color-gray-000);--border-color-accent-yellow:var(--color-yellow-600);--border-color-base:var(--color-translucent-black-200);--border-color-brand:var(--color-brand);--border-color-component-tooltip:var(--color-gray-600);--border-color-disabled:var(--color-translucent-black-400);--border-color-feedback-danger:var(--color-red-500);--border-color-feedback-danger-hover:var(--color-red-600);--border-color-feedback-danger-strong:var(--color-red-700);--border-color-feedback-danger-subtle:var(--color-red-500);--border-color-feedback-info:var(--color-blue-400);--border-color-feedback-success:var(--color-green-400);--border-color-feedback-warning:var(--color-yellow-300);--border-color-interaction-base:var(--color-translucent-black-300);--border-color-interaction-disabled:var(--color-translucent-black-100);--border-color-interaction-hover:var(--color-translucent-black-900);--border-color-interaction-inverse:var(--color-gray-900);--border-color-interaction-inverse-hover:var(--color-gray-950);--border-color-interaction-inverse-pressed:var(--color-gray-975);--border-color-interaction-pressed:var(--color-translucent-black-900);--border-color-interaction-primary-base:var(--color-green-300);--border-color-interaction-primary-hover:var(--color-green-400);--border-color-interaction-primary-selected:var(--color-green-500);--border-color-interaction-selected:var(--text-color-brand);--border-color-interaction-strong:var(--color-translucent-black-700);--nv-current-theme:light;--text-color-accent-black:var(--color-gray-1000);--text-color-accent-blue:var(--color-blue-700);--text-color-accent-blue-strong:var(--color-blue-700);--text-color-accent-blue-subtle:var(--color-blue-200);--text-color-accent-gray:var(--color-gray-700);--text-color-accent-green:var(--color-green-700);--text-color-accent-green-strong:var(--color-green-700);--text-color-accent-green-subtle:var(--color-green-200);--text-color-accent-purple:var(--color-purple-700);--text-color-accent-purple-strong:var(--color-purple-700);--text-color-accent-purple-subtle:var(--color-purple-200);--text-color-accent-red:var(--color-red-700);--text-color-accent-red-strong:var(--color-red-700);--text-color-accent-red-subtle:var(--color-red-200);--text-color-accent-teal:var(--color-teal-700);--text-color-accent-teal-strong:var(--color-teal-700);--text-color-accent-teal-subtle:var(--color-teal-200);--text-color-accent-white:var(--color-gray-000);--text-color-accent-yellow:var(--color-yellow-700);--text-color-accent-yellow-strong:var(--color-yellow-700);--text-color-accent-yellow-subtle:var(--color-yellow-200);--text-color-base:var(--color-gray-600);--text-color-brand:var(--color-brand);--text-color-component-nvidia-logo:var(--color-gray-1000);--text-color-disabled:var(--color-gray-400);--text-color-feedback-danger:var(--color-red-500);--text-color-feedback-danger-inverse:var(--color-gray-900);--text-color-feedback-danger-strong:var(--color-red-700);--text-color-feedback-danger-subtle:var(--color-red-600);--text-color-feedback-info:var(--color-blue-500);--text-color-feedback-info-inverse:var(--color-gray-900);--text-color-feedback-success:var(--color-green-500);--text-color-feedback-success-inverse:var(--color-gray-900);--text-color-feedback-warning:var(--color-yellow-300);--text-color-feedback-warning-inverse:var(--color-gray-900);--text-color-interaction-disabled-checked:var(--color-translucent-black-300);--text-color-interaction-selected:var(--color-gray-000);--text-color-inverse:var(--color-gray-000);--text-color-inverse-brand:var(--color-gray-950);--text-color-placeholder:var(--color-gray-500);--text-color-primary:var(--color-gray-1000);--text-color-secondary:var(--color-gray-600);--text-color-strong:var(--color-gray-900);--text-color-subtle:var(--color-gray-400)}.dark,.nv-dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--background-color-accent-blue:var(--color-blue-900);--background-color-accent-blue-hover:var(--color-blue-900);--background-color-accent-blue-selected:var(--color-blue-800);--background-color-accent-blue-strong:var(--color-blue-900);--background-color-accent-blue-subtle:var(--color-blue-900);--background-color-accent-blue-subtle-hover:var(--color-blue-800);--background-color-accent-blue-subtle-selected:var(--color-blue-500);--background-color-accent-gray:var(--color-gray-600);--background-color-accent-gray-hover:var(--color-gray-1000);--background-color-accent-gray-selected:var(--color-gray-200);--background-color-accent-gray-strong:var(--color-gray-700);--background-color-accent-gray-subtle:var(--color-gray-700);--background-color-accent-gray-subtle-hover:var(--color-gray-200);--background-color-accent-gray-subtle-selected:var(--color-gray-600);--background-color-accent-green:var(--color-green-900);--background-color-accent-green-hover:var(--color-green-900);--background-color-accent-green-selected:var(--color-green-800);--background-color-accent-green-strong:var(--color-green-900);--background-color-accent-green-subtle:var(--color-green-900);--background-color-accent-green-subtle-hover:var(--color-green-800);--background-color-accent-green-subtle-selected:var(--color-green-600);--background-color-accent-purple:var(--color-purple-900);--background-color-accent-purple-hover:var(--color-purple-900);--background-color-accent-purple-selected:var(--color-purple-800);--background-color-accent-purple-strong:var(--color-purple-900);--background-color-accent-purple-subtle:var(--color-purple-900);--background-color-accent-purple-subtle-hover:var(--color-purple-800);--background-color-accent-purple-subtle-selected:var(--color-purple-700);--background-color-accent-red:var(--color-red-900);--background-color-accent-red-hover:var(--color-red-900);--background-color-accent-red-selected:var(--color-red-800);--background-color-accent-red-strong:var(--color-red-900);--background-color-accent-red-subtle:var(--color-red-900);--background-color-accent-red-subtle-hover:var(--color-red-800);--background-color-accent-red-subtle-selected:var(--color-red-700);--background-color-accent-teal:var(--color-teal-900);--background-color-accent-teal-hover:var(--color-teal-900);--background-color-accent-teal-selected:var(--color-teal-800);--background-color-accent-teal-strong:var(--color-teal-900);--background-color-accent-teal-subtle:var(--color-teal-900);--background-color-accent-teal-subtle-hover:var(--color-teal-800);--background-color-accent-teal-subtle-selected:var(--color-teal-500);--background-color-accent-yellow:var(--color-yellow-900);--background-color-accent-yellow-hover:var(--color-yellow-900);--background-color-accent-yellow-selected:var(--color-yellow-800);--background-color-accent-yellow-strong:var(--color-yellow-900);--background-color-accent-yellow-subtle:var(--color-yellow-900);--background-color-accent-yellow-subtle-hover:var(--color-yellow-800);--background-color-accent-yellow-subtle-selected:var(--color-yellow-600);--background-color-background-contrast:var(--color-gray-000);--background-color-background-default:var(--color-gray-900);--background-color-background-emphasis:var(--color-gray-700);--background-color-background-highlight:var(--color-gray-600);--background-color-background-subtle:var(--color-gray-800);--background-color-component-skeleton:var(--color-gray-800);--background-color-component-skeleton-subtle:var(--color-gray-900);--background-color-component-tooltip:var(--color-gray-800);--background-color-component-track:var(--color-translucent-white-200);--background-color-component-track-inverse:var(--color-translucent-black-400);--background-color-feedback-danger:var(--color-red-900);--background-color-feedback-danger-hover:var(--color-red-600);--background-color-feedback-danger-pressed:var(--color-red-700);--background-color-feedback-danger-strong:var(--color-red-500);--background-color-feedback-danger-subtle-hover:var(--color-red-900);--background-color-feedback-danger-subtle-pressed:var(--color-red-800);--background-color-feedback-info:var(--color-blue-950);--background-color-feedback-success:var(--color-green-950);--background-color-feedback-warning:var(--color-yellow-950);--background-color-interaction-base:var(--color-translucent-black-600);--background-color-interaction-disabled:var(--color-translucent-white-100);--background-color-interaction-disabled-checked:var(--color-translucent-white-500);--background-color-interaction-hover:var(--color-translucent-white-100);--background-color-interaction-inverse:var(--color-gray-000);--background-color-interaction-inverse-hover:var(--color-gray-100);--background-color-interaction-inverse-pressed:var(--color-gray-200);--background-color-interaction-pressed:var(--color-translucent-white-200);--background-color-interaction-primary-base:var(--color-green-300);--background-color-interaction-primary-hover:var(--color-green-400);--background-color-interaction-primary-selected:var(--color-green-500);--background-color-interaction-selected:var(--color-gray-1000);--background-color-surface-base:var(--color-gray-1000);--background-color-surface-blanket:var(--color-translucent-black-700);--background-color-surface-glass:var(--color-translucent-black-600);--background-color-surface-navigation:var(--color-gray-1000);--background-color-surface-overlay:var(--color-gray-900);--background-color-surface-raised:var(--color-gray-950);--background-color-surface-sunken:var(--color-gray-975);--border-color-accent-black:var(--color-gray-1000);--border-color-accent-blue:var(--color-blue-300);--border-color-accent-gray:var(--color-gray-300);--border-color-accent-green:var(--color-green-300);--border-color-accent-purple:var(--color-purple-300);--border-color-accent-red:var(--color-red-300);--border-color-accent-teal:var(--color-teal-300);--border-color-accent-white:var(--color-gray-000);--border-color-accent-yellow:var(--color-yellow-300);--border-color-base:var(--color-translucent-white-200);--border-color-brand:var(--color-brand);--border-color-component-tooltip:var(--color-gray-600);--border-color-disabled:var(--color-translucent-white-300);--border-color-feedback-danger:var(--color-red-500);--border-color-feedback-danger-hover:var(--color-red-600);--border-color-feedback-danger-strong:var(--color-red-700);--border-color-feedback-danger-subtle:var(--color-red-300);--border-color-feedback-info:var(--color-blue-400);--border-color-feedback-success:var(--color-green-400);--border-color-feedback-warning:var(--color-yellow-200);--border-color-interaction-base:var(--color-translucent-white-200);--border-color-interaction-disabled:var(--color-translucent-white-100);--border-color-interaction-hover:var(--color-translucent-white-400);--border-color-interaction-inverse:var(--color-gray-000);--border-color-interaction-inverse-hover:var(--color-gray-100);--border-color-interaction-inverse-pressed:var(--color-gray-200);--border-color-interaction-pressed:var(--color-translucent-white-400);--border-color-interaction-primary-base:var(--color-green-300);--border-color-interaction-primary-hover:var(--color-green-400);--border-color-interaction-primary-selected:var(--color-green-500);--border-color-interaction-selected:var(--text-color-brand);--border-color-interaction-strong:var(--color-translucent-white-700);--nv-current-theme:dark;--text-color-accent-black:var(--color-gray-1000);--text-color-accent-blue:var(--color-blue-300);--text-color-accent-blue-strong:var(--color-blue-200);--text-color-accent-blue-subtle:var(--color-blue-200);--text-color-accent-gray:var(--color-gray-050);--text-color-accent-green:var(--color-green-300);--text-color-accent-green-strong:var(--color-green-200);--text-color-accent-green-subtle:var(--color-green-200);--text-color-accent-purple:var(--color-purple-200);--text-color-accent-purple-strong:var(--color-purple-200);--text-color-accent-purple-subtle:var(--color-purple-200);--text-color-accent-red:var(--color-red-300);--text-color-accent-red-strong:var(--color-red-200);--text-color-accent-red-subtle:var(--color-red-200);--text-color-accent-teal:var(--color-teal-300);--text-color-accent-teal-strong:var(--color-teal-200);--text-color-accent-teal-subtle:var(--color-teal-200);--text-color-accent-white:var(--color-gray-000);--text-color-accent-yellow:var(--color-yellow-300);--text-color-accent-yellow-strong:var(--color-yellow-200);--text-color-accent-yellow-subtle:var(--color-yellow-200);--text-color-base:var(--color-gray-200);--text-color-brand:var(--color-brand);--text-color-component-nvidia-logo:var(--color-gray-000);--text-color-disabled:var(--color-translucent-white-300);--text-color-feedback-danger:var(--color-red-400);--text-color-feedback-danger-inverse:var(--color-red-300);--text-color-feedback-danger-strong:var(--color-red-300);--text-color-feedback-danger-subtle:var(--color-red-400);--text-color-feedback-info:var(--color-blue-500);--text-color-feedback-info-inverse:var(--color-blue-300);--text-color-feedback-success:var(--color-green-400);--text-color-feedback-success-inverse:var(--color-green-300);--text-color-feedback-warning:var(--color-yellow-300);--text-color-feedback-warning-inverse:var(--color-yellow-200);--text-color-interaction-disabled-checked:var(--color-translucent-black-600);--text-color-interaction-selected:var(--color-translucent-white-200);--text-color-inverse:var(--color-gray-1000);--text-color-inverse-brand:var(--text-color-brand);--text-color-placeholder:var(--color-gray-400);--text-color-primary:var(--color-gray-000);--text-color-secondary:var(--color-gray-300);--text-color-strong:var(--color-gray-000);--text-color-subtle:var(--color-gray-400)}@media (prefers-color-scheme:dark){:where(:root){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--background-color-accent-blue:var(--color-blue-900);--background-color-accent-blue-hover:var(--color-blue-900);--background-color-accent-blue-selected:var(--color-blue-800);--background-color-accent-blue-strong:var(--color-blue-900);--background-color-accent-blue-subtle:var(--color-blue-900);--background-color-accent-blue-subtle-hover:var(--color-blue-800);--background-color-accent-blue-subtle-selected:var(--color-blue-500);--background-color-accent-gray:var(--color-gray-600);--background-color-accent-gray-hover:var(--color-gray-1000);--background-color-accent-gray-selected:var(--color-gray-200);--background-color-accent-gray-strong:var(--color-gray-700);--background-color-accent-gray-subtle:var(--color-gray-700);--background-color-accent-gray-subtle-hover:var(--color-gray-200);--background-color-accent-gray-subtle-selected:var(--color-gray-600);--background-color-accent-green:var(--color-green-900);--background-color-accent-green-hover:var(--color-green-900);--background-color-accent-green-selected:var(--color-green-800);--background-color-accent-green-strong:var(--color-green-900);--background-color-accent-green-subtle:var(--color-green-900);--background-color-accent-green-subtle-hover:var(--color-green-800);--background-color-accent-green-subtle-selected:var(--color-green-600);--background-color-accent-purple:var(--color-purple-900);--background-color-accent-purple-hover:var(--color-purple-900);--background-color-accent-purple-selected:var(--color-purple-800);--background-color-accent-purple-strong:var(--color-purple-900);--background-color-accent-purple-subtle:var(--color-purple-900);--background-color-accent-purple-subtle-hover:var(--color-purple-800);--background-color-accent-purple-subtle-selected:var(--color-purple-700);--background-color-accent-red:var(--color-red-900);--background-color-accent-red-hover:var(--color-red-900);--background-color-accent-red-selected:var(--color-red-800);--background-color-accent-red-strong:var(--color-red-900);--background-color-accent-red-subtle:var(--color-red-900);--background-color-accent-red-subtle-hover:var(--color-red-800);--background-color-accent-red-subtle-selected:var(--color-red-700);--background-color-accent-teal:var(--color-teal-900);--background-color-accent-teal-hover:var(--color-teal-900);--background-color-accent-teal-selected:var(--color-teal-800);--background-color-accent-teal-strong:var(--color-teal-900);--background-color-accent-teal-subtle:var(--color-teal-900);--background-color-accent-teal-subtle-hover:var(--color-teal-800);--background-color-accent-teal-subtle-selected:var(--color-teal-500);--background-color-accent-yellow:var(--color-yellow-900);--background-color-accent-yellow-hover:var(--color-yellow-900);--background-color-accent-yellow-selected:var(--color-yellow-800);--background-color-accent-yellow-strong:var(--color-yellow-900);--background-color-accent-yellow-subtle:var(--color-yellow-900);--background-color-accent-yellow-subtle-hover:var(--color-yellow-800);--background-color-accent-yellow-subtle-selected:var(--color-yellow-600);--background-color-background-contrast:var(--color-gray-000);--background-color-background-default:var(--color-gray-900);--background-color-background-emphasis:var(--color-gray-700);--background-color-background-highlight:var(--color-gray-600);--background-color-background-subtle:var(--color-gray-800);--background-color-component-skeleton:var(--color-gray-800);--background-color-component-skeleton-subtle:var(--color-gray-900);--background-color-component-tooltip:var(--color-gray-800);--background-color-component-track:var(--color-translucent-white-200);--background-color-component-track-inverse:var(--color-translucent-black-400);--background-color-feedback-danger:var(--color-red-900);--background-color-feedback-danger-hover:var(--color-red-600);--background-color-feedback-danger-pressed:var(--color-red-700);--background-color-feedback-danger-strong:var(--color-red-500);--background-color-feedback-danger-subtle-hover:var(--color-red-900);--background-color-feedback-danger-subtle-pressed:var(--color-red-800);--background-color-feedback-info:var(--color-blue-950);--background-color-feedback-success:var(--color-green-950);--background-color-feedback-warning:var(--color-yellow-950);--background-color-interaction-base:var(--color-translucent-black-600);--background-color-interaction-disabled:var(--color-translucent-white-100);--background-color-interaction-disabled-checked:var(--color-translucent-white-500);--background-color-interaction-hover:var(--color-translucent-white-100);--background-color-interaction-inverse:var(--color-gray-000);--background-color-interaction-inverse-hover:var(--color-gray-100);--background-color-interaction-inverse-pressed:var(--color-gray-200);--background-color-interaction-pressed:var(--color-translucent-white-200);--background-color-interaction-primary-base:var(--color-green-300);--background-color-interaction-primary-hover:var(--color-green-400);--background-color-interaction-primary-selected:var(--color-green-500);--background-color-interaction-selected:var(--color-gray-1000);--background-color-surface-base:var(--color-gray-1000);--background-color-surface-blanket:var(--color-translucent-black-700);--background-color-surface-glass:var(--color-translucent-black-600);--background-color-surface-navigation:var(--color-gray-1000);--background-color-surface-overlay:var(--color-gray-900);--background-color-surface-raised:var(--color-gray-950);--background-color-surface-sunken:var(--color-gray-975);--border-color-accent-black:var(--color-gray-1000);--border-color-accent-blue:var(--color-blue-300);--border-color-accent-gray:var(--color-gray-300);--border-color-accent-green:var(--color-green-300);--border-color-accent-purple:var(--color-purple-300);--border-color-accent-red:var(--color-red-300);--border-color-accent-teal:var(--color-teal-300);--border-color-accent-white:var(--color-gray-000);--border-color-accent-yellow:var(--color-yellow-300);--border-color-base:var(--color-translucent-white-200);--border-color-brand:var(--color-brand);--border-color-component-tooltip:var(--color-gray-600);--border-color-disabled:var(--color-translucent-white-300);--border-color-feedback-danger:var(--color-red-500);--border-color-feedback-danger-hover:var(--color-red-600);--border-color-feedback-danger-strong:var(--color-red-700);--border-color-feedback-danger-subtle:var(--color-red-300);--border-color-feedback-info:var(--color-blue-400);--border-color-feedback-success:var(--color-green-400);--border-color-feedback-warning:var(--color-yellow-200);--border-color-interaction-base:var(--color-translucent-white-200);--border-color-interaction-disabled:var(--color-translucent-white-100);--border-color-interaction-hover:var(--color-translucent-white-400);--border-color-interaction-inverse:var(--color-gray-000);--border-color-interaction-inverse-hover:var(--color-gray-100);--border-color-interaction-inverse-pressed:var(--color-gray-200);--border-color-interaction-pressed:var(--color-translucent-white-400);--border-color-interaction-primary-base:var(--color-green-300);--border-color-interaction-primary-hover:var(--color-green-400);--border-color-interaction-primary-selected:var(--color-green-500);--border-color-interaction-selected:var(--text-color-brand);--border-color-interaction-strong:var(--color-translucent-white-700);--nv-current-theme:dark;--text-color-accent-black:var(--color-gray-1000);--text-color-accent-blue:var(--color-blue-300);--text-color-accent-blue-strong:var(--color-blue-200);--text-color-accent-blue-subtle:var(--color-blue-200);--text-color-accent-gray:var(--color-gray-050);--text-color-accent-green:var(--color-green-300);--text-color-accent-green-strong:var(--color-green-200);--text-color-accent-green-subtle:var(--color-green-200);--text-color-accent-purple:var(--color-purple-200);--text-color-accent-purple-strong:var(--color-purple-200);--text-color-accent-purple-subtle:var(--color-purple-200);--text-color-accent-red:var(--color-red-300);--text-color-accent-red-strong:var(--color-red-200);--text-color-accent-red-subtle:var(--color-red-200);--text-color-accent-teal:var(--color-teal-300);--text-color-accent-teal-strong:var(--color-teal-200);--text-color-accent-teal-subtle:var(--color-teal-200);--text-color-accent-white:var(--color-gray-000);--text-color-accent-yellow:var(--color-yellow-300);--text-color-accent-yellow-strong:var(--color-yellow-200);--text-color-accent-yellow-subtle:var(--color-yellow-200);--text-color-base:var(--color-gray-200);--text-color-brand:var(--color-brand);--text-color-component-nvidia-logo:var(--color-gray-000);--text-color-disabled:var(--color-translucent-white-300);--text-color-feedback-danger:var(--color-red-400);--text-color-feedback-danger-inverse:var(--color-red-300);--text-color-feedback-danger-strong:var(--color-red-300);--text-color-feedback-danger-subtle:var(--color-red-400);--text-color-feedback-info:var(--color-blue-500);--text-color-feedback-info-inverse:var(--color-blue-300);--text-color-feedback-success:var(--color-green-400);--text-color-feedback-success-inverse:var(--color-green-300);--text-color-feedback-warning:var(--color-yellow-300);--text-color-feedback-warning-inverse:var(--color-yellow-200);--text-color-interaction-disabled-checked:var(--color-translucent-black-600);--text-color-interaction-selected:var(--color-translucent-white-200);--text-color-inverse:var(--color-gray-1000);--text-color-inverse-brand:var(--text-color-brand);--text-color-placeholder:var(--color-gray-400);--text-color-primary:var(--color-gray-000);--text-color-secondary:var(--color-gray-300);--text-color-strong:var(--color-gray-000);--text-color-subtle:var(--color-gray-400)}}.nv-density-standard{--radius-density-xl:var(--radius-xl);--spacing-density-xxs:2px;--spacing-density-xs:4px;--spacing-density-sm:6px;--spacing-density-md:8px;--spacing-density-lg:12px;--spacing-density-xl:16px;--spacing-density-2xl:24px;--spacing-density-3xl:32px;--spacing-density-4xl:48px;--spacing-density-5xl:64px}.nv-density-compact{--radius-density-xl:var(--radius-lg);--spacing-density-xxs:1px;--spacing-density-xs:2px;--spacing-density-sm:4px;--spacing-density-md:6px;--spacing-density-lg:8px;--spacing-density-xl:12px;--spacing-density-2xl:16px;--spacing-density-3xl:24px;--spacing-density-4xl:32px;--spacing-density-5xl:48px}.nv-density-spacious{--radius-density-xl:var(--radius-xl);--spacing-density-xxs:4px;--spacing-density-xs:6px;--spacing-density-sm:8px;--spacing-density-md:12px;--spacing-density-lg:16px;--spacing-density-xl:24px;--spacing-density-2xl:32px;--spacing-density-3xl:48px;--spacing-density-4xl:64px;--spacing-density-5xl:80px} diff --git a/src/uis/nvcf-ui/ui/vendor/kui-foundations/components.css b/src/uis/nvcf-ui/ui/vendor/kui-foundations/components.css new file mode 100644 index 000000000..8e3aa1c56 --- /dev/null +++ b/src/uis/nvcf-ui/ui/vendor/kui-foundations/components.css @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.nv-accordion-item>summary{list-style:none}.nv-accordion-item>summary::-webkit-details-marker{display:none}.nv-accordion-item>summary::marker{content:"";display:none}.nv-accordion-root{font-family:var(--font-sans);background:var(--nv-accordion-root-bg,var(--background-color-surface-raised));border-radius:var(--nv-accordion-root-border-radius,0);color:var(--nv-accordion-root-color,var(--text-color-primary))}.nv-accordion-root svg,.nv-accordion-root .nv-icon{color:var(--nv-accordion-icon-color,var(--text-color-base));flex-shrink:0}.nv-accordion-root [data-disabled],.nv-accordion-root [aria-disabled=true]{color:var(--text-color-disabled)}.nv-accordion-item{border-bottom:var(--nv-accordion-item-border,1px solid var(--border-color-base));display:block}.nv-accordion-item::details-content{height:0;display:block;overflow:clip}@media (prefers-reduced-motion:no-preference){.nv-accordion-item::details-content{transition:height .2s var(--ease-out),content-visibility .2s allow-discrete}}.nv-accordion-item[open]::details-content{height:auto}@media (prefers-reduced-motion:reduce){.nv-accordion-item::details-content{transition-duration:.01ms}}.nv-accordion-trigger{cursor:pointer;background:var(--nv-accordion-trigger-bg,transparent);width:100%;padding:var(--nv-accordion-trigger-padding,calc(var(--spacing)*3));justify-content:space-between;align-items:center;gap:var(--nv-accordion-trigger-gap,calc(var(--spacing)*1.5));color:var(--nv-accordion-trigger-color,inherit);font-weight:var(--font-weight-bold);font-size:var(--nv-accordion-label-font-size,var(--text-14));border:none;line-height:1.14286;display:flex}@media (prefers-reduced-motion:no-preference){.nv-accordion-trigger{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke;transition-duration:.2s;transition-timing-function:var(--ease-out)}}.nv-accordion-trigger:hover{background:var(--nv-accordion-trigger-bg-hover,var(--background-color-interaction-hover))}.nv-accordion-trigger:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}.nv-accordion-trigger[data-disabled],.nv-accordion-trigger[aria-disabled=true]{cursor:not-allowed;color:var(--text-color-disabled);background:var(--nv-accordion-trigger-bg-disabled,transparent)}.nv-accordion-trigger.nv-accordion-trigger--chevron-end{flex-direction:row;justify-content:space-between}.nv-accordion-trigger.nv-accordion-trigger--chevron-start{flex-direction:row-reverse;justify-content:flex-end}.nv-accordion-trigger svg,.nv-accordion-trigger .nv-icon{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.nv-accordion-trigger .nv-animated-chevron{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.nv-accordion-trigger .nv-accordion-label-text{text-overflow:ellipsis;white-space:nowrap;gap:inherit;overflow:hidden}.nv-accordion-content{background:var(--nv-accordion-content-bg,transparent);color:var(--nv-accordion-content-color,inherit);font-size:var(--nv-accordion-content-font-size,var(--text-14));line-height:var(--nv-accordion-content-line-height,var(--leading-lh-150));padding:var(--nv-accordion-content-padding,calc(var(--spacing)*6)calc(var(--spacing)*3))}.nv-accordion-root .nv-accordion-trigger--chevron-start+.nv-accordion-content .nv-accordion-root .nv-accordion-trigger{flex-direction:row-reverse;justify-content:flex-end}.nv-accordion-root .nv-accordion-root{background-color:var(--background-color-surface-overlay);--nv-accordion-label-font-size:var(--text-12);line-height:1.33333}.nv-anchor{cursor:pointer;width:fit-content;color:var(--text-color-primary);display:inline}.nv-anchor:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:2px;border-radius:2px}.nv-anchor:not(.nv-anchor--kind-standalone){text-decoration:underline;-webkit-text-decoration-color:var(--border-color-brand);text-decoration-color:var(--border-color-brand);text-underline-offset:4px;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;text-decoration-thickness:1px}.nv-anchor:not(.nv-anchor--disabled):not(:disabled):hover:not(:active){background-color:var(--background-color-interaction-hover)}.nv-anchor:not(.nv-anchor--disabled):not(:disabled):not(.nv-anchor--kind-standalone):hover,.nv-anchor:not(.nv-anchor--disabled):not(:disabled):not(.nv-anchor--kind-standalone):active{-webkit-text-decoration-color:var(--border-color-interaction-hover);text-decoration-color:var(--border-color-interaction-hover)}.nv-anchor:is(.nv-anchor--disabled,:disabled){cursor:not-allowed;color:var(--text-color-disabled)}.nv-anchor:is(.nv-anchor--disabled,:disabled):not(.nv-anchor--kind-standalone){-webkit-text-decoration-color:var(--text-color-disabled);text-decoration-color:var(--text-color-disabled)}.nv-anchor:is(.nv-anchor--disabled,:disabled):is(a){pointer-events:none}@media (prefers-reduced-motion:no-preference){.nv-anchor{transition-property:color,text-decoration-color;transition-duration:.2s;transition-timing-function:var(--ease-out)}}.nv-anchor [data-nv-gui-icon-before],.nv-anchor [data-nv-gui-icon-after],.nv-anchor svg{display:inline}:is(.nv-anchor [data-nv-gui-icon-before],.nv-anchor [data-nv-gui-icon-after],.nv-anchor svg):before{vertical-align:-.3em;display:inline;position:static}.nv-animated-chevron{pointer-events:none}@media (prefers-reduced-motion:no-preference){.nv-animated-chevron{transition:rotate .2s var(--ease-out)}}:where(select:open+.nv-animated-chevron),:where([data-state=open] .nv-animated-chevron),.nv-animated-chevron[data-state=open]{rotate:180deg}:where([data-state=closed] .nv-animated-chevron),.nv-animated-chevron[data-state=closed]{rotate:none}:where(select:open+.nv-animated-chevron),:where([data-state=open] .nv-animated-chevron),.nv-animated-chevron[data-state=open]{rotate:180deg}:where([data-state=closed] .nv-animated-chevron),.nv-animated-chevron[data-state=closed]{rotate:none}details[open] .nv-animated-chevron{rotate:180deg!important}details:not([open]) .nv-animated-chevron{rotate:none!important}:has(select:open) .nv-animated-chevron{rotate:180deg}.nv-app-bar-root{border-bottom:var(--border-width-1)solid var(--border-color-base);background-color:var(--background-color-surface-navigation);width:100%;padding-inline:calc(var(--spacing)*4);font-family:var(--font-sans);text-wrap:nowrap;color:var(--text-color-primary);align-items:center;gap:calc(var(--spacing)*4);height:var(--nv-app-bar-height);max-height:var(--nv-app-bar-height);display:flex;overflow:hidden}.nv-app-bar-slot-start{gap:inherit;font-size:var(--text-14);font-weight:var(--font-weight-bold);line-height:var(--leading-lh-150);flex-shrink:0;align-items:center;display:flex}.nv-app-bar-slot-center{flex-grow:1;align-items:center;height:100%;display:flex;overflow:hidden}.nv-app-bar-slot-end{gap:inherit;flex-shrink:0;align-items:center;display:flex}.nv-app-bar-expander-button.nv-button{--nv-button-icon-size:var(--text-24);--nv-button-icon-margin:-8px}.nv-avatar-root{width:calc(var(--spacing)*12);height:calc(var(--spacing)*12);background-color:var(--background-color-accent-teal-strong);font-size:var(--text-20);line-height:var(--leading-lh-150);color:var(--text-color-accent-white);font-weight:var(--font-weight-bold);border-radius:3.40282e38px;flex-shrink:0;place-items:center;display:grid;position:relative;overflow:hidden}@media (prefers-reduced-motion:no-preference){.nv-avatar-root{transition:background-color .2s var(--ease-out)}}.nv-avatar-root--size-small{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);font-size:var(--text-10)}.nv-avatar-root--size-medium{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8);font-size:var(--text-14)}.nv-avatar-root--size-large{width:calc(var(--spacing)*12);height:calc(var(--spacing)*12)}.nv-avatar-root--size-xlarge{width:calc(var(--spacing)*16);height:calc(var(--spacing)*16);font-size:var(--text-28)}.nv-avatar-root--size-xxlarge{width:calc(var(--spacing)*32);height:calc(var(--spacing)*32);font-size:var(--text-60)}.nv-avatar-root--interactive{cursor:pointer}.nv-avatar-root--interactive:hover:before{border-color:var(--border-color-interaction-hover)}.nv-avatar-root--interactive:active:before{border-color:var(--border-color-interaction-pressed)}.nv-avatar-root--interactive:not(.nv-avatar-root--kind-outline):before{opacity:0}.nv-avatar-root--interactive:not(.nv-avatar-root--kind-outline):hover:before,.nv-avatar-root--interactive:not(.nv-avatar-root--kind-outline):active:before{opacity:1}:is(.nv-avatar-root--kind-outline,.nv-avatar-root--interactive):before{content:"";inset:calc(var(--spacing)*0);border:2px solid;border-color:var(--border-color-base);border-radius:3.40282e38px;position:absolute}@media (prefers-reduced-motion:no-preference){:is(.nv-avatar-root--kind-outline,.nv-avatar-root--interactive):before{transition:border-color .2s var(--ease-out)}}:is(.nv-avatar-root--kind-outline,.nv-avatar-root--interactive).nv-avatar-root--size-small:before,:is(.nv-avatar-root--kind-outline,.nv-avatar-root--interactive).nv-avatar-root--size-medium:before{border-width:1px}:is(.nv-avatar-root--kind-outline,.nv-avatar-root--interactive).nv-avatar-root--size-xxlarge:before{border-width:4px}.nv-avatar-image{object-fit:cover;width:100%;height:100%}.nv-avatar-image:not([data-loaded]),.nv-avatar-root:has(.nv-avatar-image[data-loaded]) .nv-avatar-fallback{display:none}.nv-badge{justify-content:center;align-items:center;gap:calc(var(--spacing)*1);border-radius:var(--radius-md);width:fit-content;max-width:100%;height:fit-content;padding-inline:calc(var(--spacing)*2);font-family:var(--font-sans);font-weight:var(--font-weight-bold);--_desired-height:16px;--_font-size:var(--text-12);font-size:var(--_font-size);line-height:calc(var(--_desired-height)/var(--_font-size));vertical-align:middle;--_bg-color:transparent;--_border-color:var(--border-color-accent-blue);--_text-color:var(--text-color-accent-blue);background-color:var(--nv-badge-bg-color,var(--bg-color,var(--_bg-color)));border:1px solid var(--nv-badge-border-color,var(--border-color,var(--_border-color)));color:var(--nv-badge-text-color,var(--text-color,var(--_text-color)));flex-grow:0;flex-shrink:0;display:inline-flex}.nv-badge svg,.nv-badge .nv-icon{flex-shrink:0;width:1em;height:1em}.nv-badge.nv-badge--size-small{--_font-size:var(--text-10);--_desired-height:12px;padding-inline:calc(var(--spacing)*1)}.nv-badge.nv-badge--size-large{--_font-size:var(--text-14);--_desired-height:22px;padding-inline:calc(var(--spacing)*2)}.nv-badge.nv-badge--kind-solid{--_bg-color:var(--background-color-accent-blue);--_text-color:var(--text-color-accent-blue-strong);--_border-color:var(--background-color-accent-blue)}.nv-badge.nv-badge--color-green{--_border-color:var(--border-color-accent-green);--_text-color:var(--text-color-accent-green)}.nv-badge.nv-badge--color-green.nv-badge--kind-solid{--_bg-color:var(--background-color-accent-green);--_text-color:var(--text-color-accent-green-strong);--_border-color:var(--background-color-accent-green)}.nv-badge.nv-badge--color-red{--_border-color:var(--border-color-accent-red);--_text-color:var(--text-color-accent-red)}.nv-badge.nv-badge--color-red.nv-badge--kind-solid{--_bg-color:var(--background-color-accent-red);--_text-color:var(--text-color-accent-red-strong);--_border-color:var(--background-color-accent-red)}.nv-badge.nv-badge--color-yellow{--_border-color:var(--border-color-accent-yellow);--_text-color:var(--text-color-accent-yellow)}.nv-badge.nv-badge--color-yellow.nv-badge--kind-solid{--_bg-color:var(--background-color-accent-yellow);--_text-color:var(--text-color-accent-yellow-strong);--_border-color:var(--background-color-accent-yellow)}.nv-badge.nv-badge--color-purple{--_border-color:var(--border-color-accent-purple);--_text-color:var(--text-color-accent-purple)}.nv-badge.nv-badge--color-purple.nv-badge--kind-solid{--_bg-color:var(--background-color-accent-purple);--_text-color:var(--text-color-accent-purple-strong);--_border-color:var(--background-color-accent-purple)}.nv-badge.nv-badge--color-teal{--_border-color:var(--border-color-accent-teal);--_text-color:var(--text-color-accent-teal)}.nv-badge.nv-badge--color-teal.nv-badge--kind-solid{--_bg-color:var(--background-color-accent-teal);--_text-color:var(--text-color-accent-teal-strong);--_border-color:var(--background-color-accent-teal)}.nv-badge.nv-badge--color-gray{--_border-color:var(--border-color-accent-gray);--_text-color:var(--text-color-primary)}.nv-badge.nv-badge--color-gray.nv-badge--kind-solid{--_bg-color:var(--background-color-accent-gray-subtle);--_border-color:var(--background-color-accent-gray-subtle)}.nv-banner-root{box-sizing:border-box;border-radius:var(--radius-md);background-color:var(--bg-color);width:100%;min-height:40px;color:var(--text-color);border:1px solid var(--border-color);padding:calc(var(--spacing)*2);font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);align-items:center;display:flex;container-type:inline-size}.nv-banner-root,.nv-banner-root:where(.nv-banner-root--status-info){--bg-color:var(--background-color-feedback-info);--text-color:var(--text-color-feedback-info-inverse);--border-color:var(--border-color-feedback-info)}.nv-banner-root.nv-banner-root--status-error{--bg-color:var(--background-color-feedback-danger);--text-color:var(--text-color-feedback-danger-inverse);--border-color:var(--border-color-feedback-danger-subtle)}.nv-banner-root.nv-banner-root--status-warning{--bg-color:var(--background-color-feedback-warning);--text-color:var(--text-color-feedback-warning-inverse);--border-color:var(--border-color-feedback-warning)}.nv-banner-root.nv-banner-root--status-success{--bg-color:var(--background-color-feedback-success);--text-color:var(--text-color-feedback-success-inverse);--border-color:var(--border-color-feedback-success)}.nv-banner-root.nv-banner-root--kind-header{padding:calc(var(--spacing)*4)}.nv-banner-root.nv-banner-root--kind-header .nv-banner-icon{align-self:flex-start}.nv-banner-root.nv-banner-root--kind-header .nv-banner-heading{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-banner-root.nv-banner-root--kind-global{border-radius:var(--radius-none);border:0}.nv-banner-root.nv-banner-root--kind-global .nv-banner-content{justify-content:center}.nv-banner-root .nv-banner-icon{padding-block:calc(var(--spacing)*1);font-size:var(--text-16);display:flex}.nv-banner-root .nv-banner-header{gap:calc(var(--spacing)*1);flex-direction:column;display:flex}.nv-banner-root .nv-banner-layout{grid-template-columns:1fr auto auto;grid-template-areas:"content actions close-button";align-items:center;width:100%;display:grid}.nv-banner-root .nv-banner-layout .nv-banner-content{align-items:center;gap:calc(var(--spacing)*2);grid-area:content;width:100%;display:flex}.nv-banner-root .nv-banner-layout .nv-banner-actions-section{justify-content:flex-end;align-items:center;gap:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*2);flex-wrap:wrap;grid-area:actions;align-self:center;display:flex}.nv-banner-root .nv-banner-layout .nv-banner-close-button-section{padding-left:calc(var(--spacing)*2);grid-area:close-button;align-self:center;align-items:center;display:flex}@container (width<400px){.nv-banner-root .nv-banner-layout{grid-template-columns:1fr auto;grid-template-areas:"content close-button""actions actions"}.nv-banner-root .nv-banner-layout .nv-banner-close-button-section{place-content:start;align-self:flex-start;align-items:flex-start}.nv-banner-root .nv-banner-layout .nv-banner-content{justify-content:flex-start!important}.nv-banner-root .nv-banner-layout .nv-banner-actions-section{padding-top:calc(var(--spacing)*2);padding-left:0}}.nv-banner-root.nv-banner-root--kind-header .nv-banner-layout .nv-banner-actions-section{align-self:flex-start;align-items:flex-start}.nv-banner-root.nv-banner-root--kind-header .nv-banner-layout .nv-banner-close-button-section{place-content:start;align-self:flex-start;align-items:flex-start}.nv-banner-root.nv-banner-root--actionsPosition-bottom .nv-banner-layout{grid-template-columns:1fr auto;grid-template-areas:"content close-button""actions actions"}.nv-banner-root.nv-banner-root--actionsPosition-bottom .nv-banner-layout .nv-banner-close-button-section{place-content:start;align-self:flex-start;align-items:flex-start}.nv-banner-root.nv-banner-root--actionsPosition-bottom .nv-banner-layout .nv-banner-content{justify-content:flex-start!important}.nv-banner-root.nv-banner-root--actionsPosition-bottom .nv-banner-layout .nv-banner-actions-section{padding-top:calc(var(--spacing)*2);padding-left:0}.nv-block{max-width:100%;max-height:100%;font-family:var(--font-sans);display:block}.nv-block--overflow-auto{overflow:auto}.nv-block--overflow-clip{overflow:clip}.nv-block--overflow-hidden{overflow:hidden}.nv-block--overflow-scroll{overflow:scroll}.nv-block--overflow-visible{overflow:visible}.nv-block--overflow-x-auto{overflow-x:auto}.nv-block--overflow-x-clip{overflow-x:clip}.nv-block--overflow-x-hidden{overflow-x:hidden}.nv-block--overflow-x-scroll{overflow-x:scroll}.nv-block--overflow-x-visible{overflow-x:visible}.nv-block--overflow-y-auto{overflow-y:auto}.nv-block--overflow-y-clip{overflow-y:clip}.nv-block--overflow-y-hidden{overflow-y:hidden}.nv-block--overflow-y-scroll{overflow-y:scroll}.nv-block--overflow-y-visible{overflow-y:visible}.nv-block--text-ellipsis{text-overflow:ellipsis}.nv-block--text-clip{text-overflow:clip}.nv-block--text-wrap{text-wrap:wrap}.nv-block--text-nowrap{text-wrap:nowrap}.nv-block--text-balance{text-wrap:balance}.nv-block--text-pretty{text-wrap:pretty}.nv-breadcrumbs-root{align-items:center;gap:calc(var(--spacing)*1);font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-regular);color:var(--text-color-secondary);flex-wrap:wrap;display:flex}:is(.nv-breadcrumbs-root,.nv-breadcrumbs-root.nv-breadcrumbs-root--size-medium) .nv-breadcrumbs-separator{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);font-size:var(--text-16)}.nv-breadcrumbs-root.nv-breadcrumbs-root--size-small{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular)}.nv-breadcrumbs-root.nv-breadcrumbs-root--size-large{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular)}.nv-breadcrumbs-root.nv-breadcrumbs-root--size-large .nv-breadcrumbs-separator{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);font-size:var(--text-24)}.nv-breadcrumbs-item{align-items:center;gap:calc(var(--spacing)*1);text-underline-offset:25.0%;text-underline-position:from-font;text-decoration-thickness:10%;display:inline-flex}.nv-breadcrumbs-item.nv-breadcrumbs-item--active{color:var(--text-color-primary);font-weight:var(--font-weight-bold)}.nv-breadcrumbs-item:focus-visible,.nv-breadcrumbs-item:has(:focus-visible){outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:2px;border-radius:2px}.nv-breadcrumbs-item:not(.nv-breadcrumbs-item--active){text-decoration-line:underline;text-decoration-color:#0000}@media (hover:hover){.nv-breadcrumbs-item:not(.nv-breadcrumbs-item--active):hover{-webkit-text-decoration-color:var(--text-color-base);text-decoration-color:var(--text-color-base)}}.nv-breadcrumbs-item:not(.nv-breadcrumbs-item--active):focus-visible,.nv-breadcrumbs-item:not(.nv-breadcrumbs-item--active):has(:focus-visible){-webkit-text-decoration-color:var(--text-color-base);text-decoration-color:var(--text-color-base)}@media (prefers-reduced-motion:no-preference){.nv-breadcrumbs-item{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:var(--ease-out)}}.nv-breadcrumbs-separator{color:var(--text-color-base);line-height:var(--leading-lh-100);place-items:center;display:grid}.nv-button{cursor:pointer;background:var(--nv-button-bg,var(--_bg,var(--background-color-interaction-inverse)));border:var(--nv-button-border,var(--_border,1px solid transparent));width:fit-content;min-width:fit-content;max-width:100%;height:fit-content;color:var(--nv-button-color,var(--_color,var(--text-color-inverse)));--_padding:var(--_padding,calc(var(--spacing)*3));padding-inline:var(--nv-button-padding,var(--_padding));padding-block:calc(var(--_padding) - 2px);justify-content:center;align-items:center;gap:var(--nv-button-gap,var(--spacing));border-radius:var(--nv-button-border-radius,var(--radius-md));font-size:var(--nv-button-font-size,var(--_font-size,var(--text-14)));font-weight:var(--font-weight-bold);font-family:var(--font-sans);flex-grow:0;flex-shrink:0;display:inline-flex}.nv-button:disabled{cursor:not-allowed}@media (prefers-reduced-motion:no-preference){.nv-button{transition-property:background-color,color,scale,border-color;transition-duration:.15s;transition-timing-function:var(--ease-out)}}.nv-button:active:not(:disabled){scale:99%}.nv-button:hover:not(:disabled){background:var(--nv-button-bg-hover,var(--_bg,var(--background-color-interaction-inverse-hover)));color:var(--nv-button-color-hover,var(--_color,var(--text-color-inverse)));border:var(--nv-button-border-hover,var(--_border,1px solid transparent))}.nv-button:active:not(:disabled),.nv-button[data-state=open]:not([data-active-state=disabled]),.nv-button[aria-expanded=true]:not([data-active-state=disabled]){background:var(--nv-button-bg-active,var(--_bg,var(--background-color-interaction-inverse-pressed)));color:var(--nv-button-color-active,var(--_color,var(--text-color-inverse)));border:var(--nv-button-border-active,var(--_border,1px solid transparent))}.nv-button:disabled{background:var(--nv-button-bg-disabled,var(--_bg,var(--background-color-interaction-disabled)));color:var(--nv-button-color-disabled,var(--_color,var(--text-color-disabled)));border:var(--nv-button-border-disabled,var(--_border,1px solid transparent))}.nv-button:focus-visible,.nv-button:has(:focus-visible){outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}.nv-button svg,.nv-button .nv-icon{color:var(--nv-button-icon-color,var(--_icon-color,var(--_color,var(--text-color-inverse))));font-size:var(--nv-button-icon-size,var(--_icon-size,var(--text-16)));margin:var(--nv-button-icon-margin,var(--_icon-margin,0));flex-shrink:0}.nv-button:hover:not(:disabled) svg,.nv-button:hover:not(:disabled) .nv-icon{color:var(--nv-button-icon-color-hover,var(--_icon-color,var(--_color,var(--text-color-inverse))))}.nv-button:active:not(:disabled) svg,.nv-button:active:not(:disabled) .nv-icon,.nv-button[data-state=open]:not([data-active-state=disabled]) svg,.nv-button[data-state=open]:not([data-active-state=disabled]) .nv-icon,.nv-button[aria-expanded=true]:not([data-active-state=disabled]) svg,.nv-button[aria-expanded=true]:not([data-active-state=disabled]) .nv-icon{color:var(--nv-button-icon-color-active,var(--_icon-color,var(--_color,var(--text-color-inverse))))}.nv-button:disabled svg,.nv-button:disabled .nv-icon{color:var(--nv-button-icon-color-disabled,var(--_icon-color,var(--text-color-disabled)))}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):where(.nv-button--color-neutral){--_bg:var(--background-color-interaction-inverse);--_color:var(--text-color-inverse);--_border:1px solid transparent}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):where(.nv-button--color-brand){--_bg:var(--background-color-interaction-primary-base);--_color:var(--text-color-accent-black);--_border:1px solid transparent}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):where(.nv-button--color-danger){--_bg:var(--background-color-feedback-danger-strong);--_color:var(--text-color-accent-white);--_border:1px solid var(--border-color-feedback-danger)}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):hover:not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):hover:not(:disabled):where(.nv-button--color-neutral){--_bg:var(--background-color-interaction-inverse-hover)}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):hover:not(:disabled):where(.nv-button--color-brand){--_bg:var(--background-color-interaction-primary-hover)}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):hover:not(:disabled):where(.nv-button--color-danger){--_bg:var(--background-color-feedback-danger-hover);--_border:1px solid var(--border-color-feedback-danger-hover)}:is(:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):active:not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)),:is(:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):active:not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-neutral){--_bg:var(--background-color-interaction-inverse-pressed)}:is(:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):active:not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-brand){--_bg:var(--background-color-interaction-primary-selected)}:is(:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):active:not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-danger){--_bg:var(--background-color-feedback-danger-pressed);--_border:1px solid var(--border-color-feedback-danger-strong)}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):disabled{--_bg:var(--background-color-interaction-disabled);--_color:var(--text-color-disabled);--_border:1px solid transparent}:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):focus-visible,:is(.nv-button:not(.nv-button-group .nv-button),.nv-button:where(.nv-button--kind-primary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-primary>.nv-button)):has(:focus-visible){outline-offset:-4px}:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):where(.nv-button--color-neutral){--_bg:transparent;--_border:1px solid var(--border-color-interaction-strong);--_color:var(--text-color-primary)}:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):where(.nv-button--color-brand){--_bg:transparent;--_border:1px solid var(--border-color-brand);--_color:var(--text-color-primary);--_icon-color:var(--text-color-brand)}:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):where(.nv-button--color-danger){--_bg:transparent;--_border:1px solid var(--border-color-feedback-danger);--_color:var(--text-color-feedback-danger)}:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):hover:not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):hover:not(:disabled):where(.nv-button--color-neutral),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):hover:not(:disabled):where(.nv-button--color-brand){--_bg:var(--background-color-interaction-hover)}:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):hover:not(:disabled):where(.nv-button--color-danger){--_bg:var(--background-color-feedback-danger-subtle-hover);--_color:var(--text-color-feedback-danger-subtle)}:is(:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)),:is(:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-neutral),:is(:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-brand){--_bg:var(--background-color-interaction-pressed)}:is(:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-danger){--_bg:var(--background-color-feedback-danger-subtle-pressed);--_color:var(--text-color-feedback-danger-strong);--_border:1px solid var(--border-color-feedback-danger)}:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):disabled{--_bg:transparent;--_border:1px solid var(--border-color-interaction-disabled);--_color:var(--text-color-disabled);--_icon-color:var(--text-color-disabled)}:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):focus-visible,:is(.nv-button:where(.nv-button--kind-secondary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-secondary>.nv-button)):has(:focus-visible){outline-offset:-2px}.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button){--_bg:transparent;--_border:1px solid transparent;--_color:var(--text-color-primary)}:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):where(.nv-button--color-brand){--_icon-color:var(--text-color-brand)}:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):where(.nv-button--color-danger){--_color:var(--text-color-feedback-danger)}:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):hover:not(:disabled){--_bg:var(--background-color-interaction-hover)}:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):hover:not(:disabled):where(.nv-button--color-danger){--_bg:var(--background-color-feedback-danger-subtle-hover);--_color:var(--text-color-feedback-danger-subtle);--_border:1px solid var(--background-color-feedback-danger-subtle-hover)}:is(:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)),:is(:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-neutral),:is(:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-brand){--_bg:var(--background-color-interaction-pressed)}:is(:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):active:not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[data-state=open]:not([data-active-state=disabled]):not(:disabled),:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button))[aria-expanded=true]:not([data-active-state=disabled]):not(:disabled)):where(.nv-button--color-danger){--_bg:var(--background-color-feedback-danger-subtle-pressed);--_color:var(--text-color-feedback-danger-strong);--_border:1px solid transparent}:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):disabled{--_bg:transparent;--_color:var(--text-color-disabled);--_icon-color:var(--text-color-disabled)}:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):focus-visible,:is(.nv-button:where(.nv-button--kind-tertiary):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--kind-tertiary>.nv-button)):has(:focus-visible){outline-offset:-2px}.nv-button,.nv-button:where(.nv-button--size-medium):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--size-medium>.nv-button){--_padding:calc(var(--spacing)*3);--_font-size:var(--text-14);--_icon-size:var(--text-16);--_icon-margin:-1px;line-height:var(--nv-button-line-height,var(--_line-height,calc(16/14)));min-height:calc(var(--spacing)*10);min-width:calc(var(--spacing)*10)}.nv-button:where(.nv-button--size-tiny):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--size-tiny>.nv-button){--_padding:var(--spacing);--_font-size:var(--text-12);--_icon-size:var(--text-12);--_line-height:1;min-height:calc(var(--spacing)*5);min-width:calc(var(--spacing)*5)}.nv-button:where(.nv-button--size-small):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--size-small>.nv-button){--_padding:calc(var(--spacing)*2);--_font-size:var(--text-12);--_icon-size:var(--text-12);--_line-height:1;min-height:calc(var(--spacing)*7);min-width:calc(var(--spacing)*7)}.nv-button:where(.nv-button--size-large):not(.nv-button-group .nv-button),.nv-button:where(.nv-button-group--size-large>.nv-button){--_padding:calc(var(--spacing)*4);--_font-size:var(--text-16);--_icon-size:var(--text-16);--_line-height:1;min-height:calc(var(--spacing)*12);min-width:calc(var(--spacing)*12)}.nv-card-root{border:1px solid;border-color:var(--border-color-base);text-align:left;height:100%;min-height:fit-content;font-family:var(--font-sans);color:var(--text-color-primary);border-radius:var(--radius-density-xl);background-color:var(--background-color-surface-raised);box-shadow:var(--shadow-md);background-clip:content-box;flex-direction:column;display:flex;position:relative;overflow:hidden}.nv-card-root.nv-card-root--layout-horizontal{flex-direction:row}.nv-card-root .nv-card-content{padding:var(--spacing-density-2xl)}.nv-card-root .nv-card-media{aspect-ratio:400/234}.nv-card-root.nv-card-root--kind-float .nv-card-media{border-radius:var(--radius-xl);background-color:var(--background-color-surface-raised);box-shadow:var(--shadow-md);background-clip:content-box;border:1px solid #0000;overflow:hidden}@media (prefers-reduced-motion:no-preference){.nv-card-root{transition-property:background-color,border-color,box-shadow;transition-duration:.2s;transition-timing-function:var(--ease-out)}}.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):hover,.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):focus-visible{cursor:pointer}:is(.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):hover,.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):focus-visible):not(.nv-card-root--kind-float){background-color:var(--background-color-surface-overlay)}:is(.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):hover,.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):focus-visible):not(.nv-card-root--kind-float),:is(.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):hover,.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):focus-visible).nv-card-root--kind-float .nv-card-media{border:1px solid;border-color:var(--border-color-interaction-hover);box-shadow:var(--shadow-lg)}.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):focus-visible{outline:none}.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):focus-visible:not(.nv-card-root--kind-float),.nv-card-root.nv-card-root--interactive:not(.nv-card-root--selected):focus-visible.nv-card-root--kind-float .nv-card-media{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}.nv-card-root.nv-card-root--selected:not(.nv-card-root--kind-float),.nv-card-root.nv-card-root--selected.nv-card-root--kind-float .nv-card-media{border-color:var(--border-color-interaction-selected);box-shadow:var(--shadow-lg)}.nv-card-root.nv-card-root--kind-gradient .nv-card-content{padding:var(--spacing-density-2xl)}.nv-card-root.nv-card-root--kind-gradient .nv-card-media{-webkit-mask-image:linear-gradient(#000 65%,#0000 98%);mask-image:linear-gradient(#000 65%,#0000 98%)}.nv-card-root.nv-card-root--kind-gradient.nv-card-root--layout-horizontal .nv-card-media{aspect-ratio:400/186;-webkit-mask-image:linear-gradient(90deg,#000 58%,#0000 87%);mask-image:linear-gradient(90deg,#000 58%,#0000 87%)}.nv-card-root.nv-card-root--kind-float{gap:var(--spacing-density-xl);border-radius:var(--radius-none);box-shadow:none;background-color:#0000;border:0;overflow:visible}.nv-card-root.nv-card-root--kind-float .nv-card-content{padding:calc(var(--spacing)*0)}.nv-card-root.nv-card-root--kind-float.nv-card-root--layout-horizontal .nv-card-media{aspect-ratio:2}.nv-card-root.nv-card-root--kind-float .nv-card-media{aspect-ratio:400/234}.nv-card-root .nv-card-media{flex:1;transition:inherit;position:relative;overflow:hidden}.nv-card-root .nv-card-media>img,.nv-card-root .nv-card-media>video{object-fit:cover;width:100%;height:100%;position:absolute}.nv-card-root .nv-card-media-header{inset:calc(var(--spacing)*4);position:absolute}.nv-card-root .nv-card-content-header,.nv-card-root .nv-card-media-header{gap:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}.nv-card-root .nv-card-content{align-items:flex-start;gap:var(--spacing-density-xl);flex:1;height:fit-content;display:grid}.nv-carousel-root{--nv-carousel-item-gap:calc(var(--spacing)*4);--_gap:var(--nv-carousel-gap,calc(var(--spacing)*3));flex-direction:column;width:100%;display:flex}.nv-carousel-items{overscroll-behavior-x:contain;gap:var(--nv-carousel-item-gap);scroll-snap-type:x mandatory;width:100%;scroll-padding-inline:var(--nv-carousel-scroll-padding,0);scrollbar-width:none;display:flex;overflow-x:auto}.nv-carousel-header+.nv-carousel-items{padding-top:var(--_gap)}.nv-carousel-items:has(+.nv-carousel-footer){padding-bottom:var(--_gap)}@media (prefers-reduced-motion:no-preference){.nv-carousel-items{scroll-behavior:smooth}}.nv-carousel-items::-webkit-scrollbar{display:none}.nv-carousel-items:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:2px}.nv-carousel-item{min-width:calc(var(--spacing)*0);flex:0 0 var(--nv-carousel-item-width,auto);scroll-snap-align:start}.nv-carousel-controls{align-items:center;gap:calc(var(--spacing)*2);display:flex}.nv-carousel-arrow-button{flex-shrink:0}.nv-carousel-arrow-button[aria-disabled=true]{cursor:not-allowed}.nv-carousel-arrow-button[aria-disabled=true],.nv-carousel-arrow-button[aria-disabled=true]:hover,.nv-carousel-arrow-button[aria-disabled=true]:active{background:var(--nv-button-bg-disabled,var(--background-color-interaction-disabled));color:var(--nv-button-color-disabled,var(--text-color-disabled));border:var(--nv-button-border-disabled,1px solid transparent)}.nv-carousel-arrow-button[aria-disabled=true] svg,.nv-carousel-arrow-button[aria-disabled=true] .nv-icon,.nv-carousel-arrow-button[aria-disabled=true]:hover svg,.nv-carousel-arrow-button[aria-disabled=true]:hover .nv-icon,.nv-carousel-arrow-button[aria-disabled=true]:active svg,.nv-carousel-arrow-button[aria-disabled=true]:active .nv-icon{color:var(--nv-button-icon-color-disabled,var(--text-color-disabled))}.nv-carousel-indicator{margin-inline:calc(var(--spacing)*2);align-items:center;gap:calc(var(--spacing)*2);display:flex}.nv-carousel-indicator--pending{visibility:hidden}.nv-carousel-indicator-bar{margin:calc(var(--spacing)*0);box-sizing:content-box;cursor:pointer;appearance:none;padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*2);width:var(--nv-carousel-indicator-bar-width,calc(var(--spacing)*12));height:var(--nv-carousel-indicator-bar-height,calc(var(--spacing)*1.5));background:0 0;border:0;display:block}.nv-carousel-indicator-bar:before{content:"";background:var(--nv-carousel-indicator-track,var(--background-color-component-track));border-radius:3.40282e38px;width:100%;height:100%;display:block}.nv-carousel-indicator-bar:focus-visible{outline:none}.nv-carousel-indicator-bar:focus-visible:before{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:2px}.nv-carousel-indicator-bar--active:before{background:var(--nv-carousel-indicator-active,var(--background-color-interaction-primary-base))}.nv-carousel-indicator-count{font-size:var(--text-14);white-space:nowrap;color:var(--text-color-secondary);font-weight:var(--font-weight-regular)}.nv-checkbox-root{align-items:flex-start;gap:calc(var(--spacing)*2);width:fit-content;display:flex}.nv-checkbox-root .nv-label,.nv-checkbox-root label{line-height:1.14286}.nv-checkbox-root.nv-checkbox-root--label-left{flex-direction:row-reverse}.nv-checkbox-root:has(:disabled){cursor:not-allowed}.nv-checkbox-input{border:2px solid var(--border-color-interaction-base);width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);cursor:pointer;appearance:none;border-radius:var(--radius-md);background-color:var(--background-color-interaction-base);flex-shrink:0;position:relative}.nv-checkbox-input:focus-visible{outline:2px solid var(--nv-focus-outline-color,var(--text-color-primary));outline-offset:2px}.nv-checkbox-input:before{content:"";inset:calc(var(--spacing)*0);color:var(--text-color-accent-black);opacity:0;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 80%,70% 80%,70% 100%);background-color:currentColor;width:70%;height:70%;margin:auto;display:block;position:absolute;rotate:45deg}.nv-checkbox-input:hover{border-color:var(--border-color-interaction-hover);background-color:var(--background-color-interaction-hover)}.nv-checkbox-input:active{border-color:var(--border-color-interaction-pressed);background-color:var(--background-color-interaction-selected)}.nv-checkbox-input:is(:disabled,[aria-disabled=true]){border-color:var(--border-color-interaction-disabled);pointer-events:none;cursor:not-allowed;background-color:var(--background-color-interaction-disabled)}.nv-checkbox-input:is(:checked,[data-state=checked]){background-color:var(--background-color-interaction-primary-base);border:1px solid #0000}.nv-checkbox-input:is(:checked,[data-state=checked]):before{opacity:1;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 0%,70% 0%,70% 100%)}.nv-checkbox-input:is(:checked,[data-state=checked]):hover{background-color:var(--background-color-interaction-primary-hover)}.nv-checkbox-input:is(:checked,[data-state=checked]):active{background-color:var(--background-color-interaction-primary-selected)}.nv-checkbox-input:is(:checked,[data-state=checked]):disabled,.nv-checkbox-input:is(:checked,[data-state=checked])[aria-disabled=true]{background-color:var(--background-color-interaction-disabled-checked)}:is(.nv-checkbox-input:is(:checked,[data-state=checked]):disabled,.nv-checkbox-input:is(:checked,[data-state=checked])[aria-disabled=true]):before{color:var(--text-color-inverse)}.nv-checkbox-input:indeterminate{background-color:var(--background-color-interaction-primary-base);border:1px solid #0000}.nv-checkbox-input:indeterminate:before{opacity:1;clip-path:polygon(10% 60%,10% 40%,90% 40%,90% 60%);rotate:none}.nv-checkbox-input:indeterminate:hover{background-color:var(--background-color-interaction-primary-hover)}.nv-checkbox-input:indeterminate:active{background-color:var(--background-color-interaction-primary-selected)}.nv-checkbox-input:indeterminate:disabled,.nv-checkbox-input:indeterminate[aria-disabled=true]{background-color:var(--background-color-interaction-disabled-checked)}:is(.nv-checkbox-input:indeterminate:disabled,.nv-checkbox-input:indeterminate[aria-disabled=true]):before{color:var(--text-color-inverse)}@media (prefers-reduced-motion:no-preference){.nv-checkbox-input{transition-duration:.15s;transition-timing-function:var(--ease-out);transition-property:background-color,border-width,border-color}.nv-checkbox-input:before{transition:clip-path var(--ease-out),opacity var(--ease-out),transform var(--ease-out)}}@media (forced-colors:active){.nv-checkbox-input:is(:checked,:indeterminate):before{clip-path:none;background-color:#0000;justify-content:center;align-items:center;font-size:.75rem;line-height:1;display:flex;rotate:none}.nv-checkbox-input:is(:checked,[data-state=checked]):before{content:"\2713"}.nv-checkbox-input:indeterminate:before{content:"\2013"}}@media print{.nv-checkbox-input:is(:checked,[data-state=checked]):before{content:"\2713";clip-path:none;background-color:#0000;rotate:none}.nv-checkbox-input:indeterminate:before{content:"\2013";clip-path:none;background-color:#0000;rotate:none}}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]){border-color:var(--border-color-feedback-danger)}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):hover{background-color:var(--background-color-feedback-danger-subtle-hover)}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):active{background-color:var(--background-color-feedback-danger-subtle-pressed)}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):is(:checked,[data-state=checked]){background-color:var(--background-color-feedback-danger-strong);border-color:#0000}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):is(:checked,[data-state=checked]):hover{background-color:var(--background-color-feedback-danger-hover)}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):is(:checked,[data-state=checked]):active{background-color:var(--background-color-feedback-danger-pressed)}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):indeterminate{background-color:var(--background-color-feedback-danger-strong);border-color:#0000}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):indeterminate:hover{background-color:var(--background-color-feedback-danger-hover)}.nv-checkbox-input--error.nv-checkbox-input:not(:disabled):not([aria-disabled=true]):indeterminate:active{background-color:var(--background-color-feedback-danger-pressed)}.nv-code-snippet-root{--nv-code-snippet-custom-background:var(--background-color-surface-base);gap:calc(var(--spacing)*1);border-radius:var(--radius-md);min-height:fit-content;font-family:var(--font-mono);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);flex-direction:column;display:flex;position:relative;overflow:hidden}.nv-code-snippet-root .nv-code-snippet-actions{justify-content:flex-end;align-items:center;display:flex}.nv-code-snippet-root.nv-code-snippet-root--collapsible:not(.nv-code-snippet-root--open):after{content:"";height:calc(var(--nv-code-snippet-rows,4)*1lh);pointer-events:none;background-color:var(--nv-code-snippet-custom-background);position:absolute;bottom:1px;left:1px;right:1px;-webkit-mask-image:linear-gradient(#0000 0% 0%,#0003 60%,#0009 80%,#000 100%);mask-image:linear-gradient(#0000 0% 0%,#0003 60%,#0009 80%,#000 100%)}.nv-code-snippet-root.nv-code-snippet-root--kind-inline{width:fit-content;padding:calc(var(--spacing)*0);align-items:center;display:inline-flex}.nv-code-snippet-root.nv-code-snippet-root--kind-inline .nv-code-snippet-code{padding:calc(var(--spacing)*0)}.nv-code-snippet-root .nv-code-snippet-code{border-radius:var(--radius-md);border:1px solid;border-color:var(--border-color-base);background-color:var(--nv-code-snippet-custom-background);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);opacity:1;transition:opacity var(--ease-out)ease-in-out;scrollbar-width:thin;scrollbar-color:var(--nv-scrollbar-color);align-content:center;align-self:stretch;overflow:auto}.nv-code-snippet-root .nv-code-snippet-code.nv-code-snippet-code--loading{opacity:0}.nv-code-snippet-root .nv-code-snippet-copy-button{justify-content:flex-end}.nv-code-snippet-root.nv-code-snippet-root--with-rows:not(.nv-code-snippet-root--open) .nv-code-snippet-code{max-height:calc(var(--nv-code-snippet-rows)*1lh);position:relative;overflow-y:auto}.nv-code-snippet-root.nv-code-snippet-root--collapsible{position:relative}.nv-code-snippet-root.nv-code-snippet-root--collapsible.nv-code-snippet-root--open .nv-code-snippet-code{max-height:none}.nv-code-snippet-root.nv-code-snippet-root--collapsible .nv-code-snippet-code.nv-code-snippet-code--collapsed{max-height:calc(var(--nv-code-snippet-rows,4)*1lh);position:relative;overflow-y:auto}.nv-collapsible-root>summary{list-style:none}.nv-collapsible-root>summary::-webkit-details-marker{display:none}.nv-collapsible-root>summary::marker{content:"";display:none}.nv-collapsible-root::details-content{height:0;transition:height .2s var(--ease-out),content-visibility .2s allow-discrete;display:block;overflow:clip}.nv-collapsible-root[open]::details-content{height:auto}@media (prefers-reduced-motion:reduce){.nv-collapsible-root::details-content{transition-duration:.01ms}}.nv-collapsible-trigger-wrapper{list-style:none}.nv-collapsible-trigger-wrapper::-webkit-details-marker{display:none}.nv-collapsible-trigger-wrapper::marker{content:"";display:none}.nv-collapsible-trigger[data-disabled],.nv-collapsible-trigger[aria-disabled=true]{pointer-events:none;cursor:not-allowed;opacity:.5}.nv-collapsible-trigger[data-state=open] .group-data-\[state\=open\]\:hidden{display:none}.nv-collapsible-trigger[data-state=closed] .group-data-\[state\=closed\]\:hidden{display:none}.nv-collapsible-content-inner{padding:var(--nv-collapsible-content-padding,0)}.nv-combobox-trigger{anchor-name:var(--nv-anchor-self,--nv-anchor-none),var(--nv-anchor-tooltip,--nv-anchor-none)}@media (scripting:enabled){.nv-combobox-native-fallback{display:none}}@media (scripting:none){[data-combobox-enhanced]{display:none!important}}.nv-combobox-content[popover]{color:inherit;background:0 0;border:0;margin:0;padding:0;display:none;position:fixed;inset:auto;overflow:visible}.nv-combobox-content[popover]:popover-open{display:block}.nv-combobox-content[popover].\:popover-open{display:block}.nv-combobox-content{--menu-translate-start:0 calc(var(--transition-offset)*-1);transform-origin:top}@supports (position-anchor:--a){.nv-combobox-content{--nv-combobox-offset:4px;width:anchor-size(width);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline;margin:0}.nv-combobox-content[data-side=bottom]{margin-top:var(--nv-combobox-offset);position-area:bottom}.nv-combobox-content[data-side=top]{margin-bottom:var(--nv-combobox-offset);position-area:top}.nv-combobox-content[data-side=left]{margin-right:var(--nv-combobox-offset);position-area:left}.nv-combobox-content[data-side=right]{margin-left:var(--nv-combobox-offset);position-area:right}}.nv-combobox-content[data-side=top]{--menu-translate-start:0 calc(var(--transition-offset));transform-origin:bottom}.nv-combobox-content[data-side=left]{--menu-translate-start:var(--transition-offset)0;transform-origin:100%}.nv-combobox-content[data-side=right]{--menu-translate-start:calc(var(--transition-offset)*-1)0;transform-origin:0}.nv-combobox-content .nv-menu-root{overscroll-behavior:contain;max-height:min(320px,100vh - 2rem)}.nv-combobox-content .nv-menu-item[data-active-item]{background-color:var(--background-color-interaction-hover)}@keyframes combobox-in{0%{translate:var(--menu-translate-start);opacity:0}to{opacity:1;translate:0}}@media (prefers-reduced-motion:no-preference){.nv-combobox-content:popover-open{animation:combobox-in .25s var(--ease-out)}}.nv-input-shell.nv-combobox-trigger--multiple{--nv-combobox-trigger-max-height:calc(var(--nv-input-height)*3.5);height:auto;min-height:var(--nv-input-height);padding-block:calc(var(--nv-input-padding)/2)}.nv-input-shell.nv-combobox-trigger--multiple input:disabled,.nv-input-shell.nv-combobox-trigger--multiple input[readonly]{flex:0 0 0;min-width:0}.nv-combobox-trigger-field{align-items:center;gap:inherit;min-width:0;max-height:var(--nv-combobox-trigger-max-height);scrollbar-width:thin;scrollbar-color:var(--nv-scrollbar-color);flex-wrap:wrap;flex-grow:1;display:flex;overflow:hidden auto}.nv-combobox-trigger-field input{flex:1 0 6rem;min-width:2.5rem}.nv-combobox-trigger-field .nv-combobox-selected-tag{max-width:100%}.nv-combobox-trigger-field .nv-combobox-selected-tag-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.nv-combobox-native-fallback{padding-inline:var(--nv-input-padding)}.nv-date-picker-content{margin-block:calc(var(--spacing)*1);overflow-y:auto;padding:calc(var(--spacing)*0)!important}.nv-date-picker-trigger{width:100%}.nv-date-picker-trigger.nv-date-picker-trigger--kind-range{width:100%}.nv-date-picker-trigger.nv-date-picker-trigger--kind-range.nv-date-picker-trigger.nv-date-picker-trigger--kind-range>*{height:var(--nv-input-height)!important}@media (scripting:enabled){.nv-date-picker-native-fallback{display:none}}@media (scripting:none){.nv-date-picker-trigger [data-date-picker-enhanced]{display:none!important}.nv-date-picker-native-fallback{appearance:auto;width:100%;height:100%;font:inherit;letter-spacing:inherit;word-spacing:inherit;background-color:#0000;border:0;outline:none}.nv-date-picker-content{display:none!important}}.nv-date-picker-calendar-dropdown-container{justify-content:space-between;align-items:center;width:100%;display:flex}.nv-date-picker-calendar-header-container{justify-content:space-between;align-items:center;display:inline-flex}.nv-date-picker-calendar-caption{height:calc(var(--spacing)*8);width:100%;padding-top:calc(var(--spacing)*4.5);font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-semibold);justify-content:center;align-items:center;display:flex;position:relative}.nv-date-picker-calendar-caption.nv-date-picker-calendar-caption--range{width:fit-content;margin-inline:auto}.nv-date-picker-calendar-weekday{height:var(--spacing-density-3xl);width:var(--spacing-density-3xl);vertical-align:bottom;font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);color:var(--text-color-placeholder)}.nv-date-picker-calendar-prev-button{top:calc(var(--spacing)*4);left:calc(var(--spacing)*4);z-index:auto;position:absolute}.nv-date-picker-calendar-next-button{top:calc(var(--spacing)*4);right:calc(var(--spacing)*4);z-index:auto;position:absolute}.nv-date-picker-calendar-prev-button,.nv-date-picker-calendar-next-button>.nv-icon{--icon-font-size:var(--text-16);--icon-size:var(--text-16)}.nv-date-picker-calendar-table{border-collapse:collapse;gap:var(--spacing-density-xs);width:100%}.nv-date-picker-calendar-month{gap:var(--spacing-density-sm);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);flex-direction:column;display:flex}.nv-date-picker-calendar-month:not(:first-child){padding:calc(var(--spacing)*0)}@media (width>=36rem){.nv-date-picker-calendar-month:not(:first-child){padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2)}}.nv-date-picker-calendar-month:not(:first-child) .nv-date-picker-calendar-caption{display:none}@media (width>=36rem){.nv-date-picker-calendar-month:not(:first-child) .nv-date-picker-calendar-caption{display:flex}}.nv-date-picker-calendar-month:not(:first-child) table{display:none}@media (width>=36rem){.nv-date-picker-calendar-month:not(:first-child) table{display:table}}.nv-date-picker-calendar-months{flex-direction:column;display:flex}@media (width>=36rem){.nv-date-picker-calendar-months{gap:var(--spacing-density-sm);flex-direction:row}}.nv-date-picker-calendar-footer{padding-inline:calc(var(--spacing)*4);padding-bottom:calc(var(--spacing)*4)}.nv-date-picker-calendar-week:not(:first-child){border-top:1px solid #0000}.nv-date-picker-calendar-cell{height:var(--spacing-density-3xl);width:var(--spacing-density-3xl);border-radius:var(--radius-lg);padding:calc(var(--spacing)*0);text-align:center;position:relative;overflow:hidden}.nv-date-picker-calendar-cell:focus-within{position:relative}.nv-date-picker-calendar-cell[aria-selected]>.nv-date-picker-calendar-day--selected:not(.nv-date-picker-calendar-day--disabled):not(.nv-date-picker-calendar-day--outside){color:var(--text-color-primary)}.nv-date-picker-calendar-cell.nv-date-picker-calendar-cell--range.nv-date-picker-calendar-range-end{border-top-left-radius:var(--radius-none);border-bottom-left-radius:var(--radius-none)}.nv-date-picker-calendar-cell.nv-date-picker-calendar-cell--range.nv-date-picker-calendar-range-start{border-top-right-radius:var(--radius-none);border-bottom-right-radius:var(--radius-none)}.nv-date-picker-calendar-cell.nv-date-picker-calendar-cell--range.nv-date-picker-calendar-range-middle{border-radius:var(--radius-none)}.nv-date-picker-calendar-cell.nv-date-picker-calendar-cell--range.nv-date-picker-calendar-range-start.nv-date-picker-calendar-range-end{border-radius:var(--radius-lg)}.nv-date-picker-calendar-day{height:var(--spacing-density-3xl);width:var(--spacing-density-3xl);cursor:pointer;font-size:var(--text-14);color:var(--text-color-primary);justify-content:center;align-items:center;display:flex}.nv-date-picker-calendar-day.nv-date-picker-calendar-day--outside{pointer-events:none;color:var(--text-color-placeholder)}.nv-date-picker-calendar-day.nv-date-picker-calendar-day--selected:not(.nv-date-picker-calendar-day--disabled):not(.nv-date-picker-calendar-day--outside){background-color:var(--background-color-interaction-pressed);font-weight:var(--font-weight-bold)}.nv-date-picker-calendar-day.nv-date-picker-calendar-day--disabled{cursor:not-allowed;color:var(--text-color-placeholder)}.nv-date-picker-calendar-day:not(.nv-date-picker-calendar-day--outside):not(.nv-date-picker-calendar-day--disabled):not(.nv-date-picker-calendar-day--selected):hover{background-color:var(--background-color-interaction-hover)}.nv-date-picker-calendar-day:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px;border-radius:inherit}.nv-date-picker-calendar-dropdown{visibility:hidden;height:calc(var(--spacing)*48);width:calc(var(--spacing)*46)}.nv-date-picker-calendar-dropdown[data-visible=true]{visibility:visible}.nv-date-picker-calendar-dropdown .nv-menu-checkbox-item .nv-checkbox-input{display:none}.nv-divider-root{flex-grow:1;flex-shrink:0;flex-basis:calc(var(--spacing)*0);align-items:stretch;width:100%;height:100%;display:flex}.nv-divider-root:has(.nv-divider-element--orientation-horizontal){align-items:center}.nv-divider-root:has(.nv-divider-element--orientation-vertical){justify-content:center}.nv-divider-element{margin:calc(var(--spacing)*0);border-style:solid;list-style-type:none}.nv-divider-element.nv-divider-element--orientation-horizontal{border-bottom:1px solid;border-bottom-color:var(--border-color-base);width:100%}.nv-divider-element.nv-divider-element--orientation-horizontal.nv-divider-element--width-medium{border-bottom-width:2px}.nv-divider-element.nv-divider-element--orientation-horizontal.nv-divider-element--width-large{border-bottom-width:4px}.nv-divider-element.nv-divider-element--orientation-vertical{border-left:1px solid;border-left-color:var(--border-color-base);min-height:1em}.nv-divider-element.nv-divider-element--orientation-vertical.nv-divider-element--width-medium{border-left-width:2px}.nv-divider-element.nv-divider-element--orientation-vertical.nv-divider-element--width-large{border-left-width:4px}.nv-dropdown-content[popover]{margin:0;display:none;position:fixed;inset:auto}.nv-dropdown-content[popover]:popover-open{flex-direction:column;display:flex}.nv-dropdown-content[popover].\:popover-open{flex-direction:column;display:flex}.nv-dropdown-trigger{anchor-name:var(--nv-anchor-self,--nv-anchor-none),var(--nv-anchor-tooltip,--nv-anchor-none);align-items:center;gap:var(--spacing);display:inline-flex}@supports (position-anchor:--a){.nv-dropdown-content{--nv-dropdown-offset:4px;position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline;margin:0}.nv-dropdown-content[data-side=bottom]{margin-top:var(--nv-dropdown-offset);position-area:bottom span-right}.nv-dropdown-content[data-side=bottom][data-align=center]{position-area:bottom}.nv-dropdown-content[data-side=bottom][data-align=end]{position-area:bottom span-left}.nv-dropdown-content[data-side=top]{margin-bottom:var(--nv-dropdown-offset);position-area:top span-right}.nv-dropdown-content[data-side=top][data-align=center]{position-area:top}.nv-dropdown-content[data-side=top][data-align=end]{position-area:top span-left}.nv-dropdown-content[data-side=left]{margin-right:var(--nv-dropdown-offset);position-area:left span-bottom}.nv-dropdown-content[data-side=left][data-align=center]{position-area:left}.nv-dropdown-content[data-side=left][data-align=end]{position-area:left span-top}.nv-dropdown-content[data-side=right]{margin-left:var(--nv-dropdown-offset);position-area:right span-bottom}.nv-dropdown-content[data-side=right][data-align=center]{position-area:right}.nv-dropdown-content[data-side=right][data-align=end]{position-area:right span-top}}.nv-dropdown-sub{anchor-name:var(--nv-anchor-self,--nv-anchor-none),var(--nv-anchor-tooltip,--nv-anchor-none);width:100%;display:flex;position:relative}.nv-dropdown-sub-content[popover]{margin:0;display:none;position:fixed}.nv-dropdown-sub-content[popover]:popover-open{flex-direction:column;display:flex}.nv-dropdown-sub-content[popover].\:popover-open{flex-direction:column;display:flex}.nv-dropdown-sub-content{overscroll-behavior:contain}@supports (position-anchor:--a){.nv-dropdown-sub-content{position-area:right span-bottom;position-try-fallbacks:flip-inline,flip-block,flip-inline flip-block;margin:0}}@keyframes dropdown-in{0%{translate:var(--menu-translate-start);opacity:0}to{opacity:1;translate:0}}@keyframes dropdown-out{0%{opacity:1;translate:0}to{translate:var(--menu-translate-start);opacity:0}}.nv-dropdown-content{overscroll-behavior:contain;--menu-translate-start:0 calc(var(--transition-offset)*-1);transform-origin:top;max-height:calc(100vh - 2rem);overflow-y:auto}@supports (position-anchor:--a){.nv-dropdown-content{max-height:min(calc(100vh - 2rem),calc(100.0% - var(--nv-dropdown-offset)))}}.nv-dropdown-content[data-side=top]{--menu-translate-start:0 var(--transition-offset);transform-origin:bottom}.nv-dropdown-content[data-side=left]{--menu-translate-start:var(--transition-offset)0;transform-origin:100%}.nv-dropdown-content[data-side=right]{--menu-translate-start:calc(var(--transition-offset)*-1)0;transform-origin:0}@media (prefers-reduced-motion:no-preference){.nv-dropdown-content:popover-open{animation:dropdown-in .25s var(--ease-out)}}@media (prefers-reduced-motion:no-preference){.nv-dropdown-content.\:popover-open{animation:dropdown-in .25s var(--ease-out)}}.nv-dropdown-sub-content{--transition-offset:8px;--submenu-translate-start:calc(var(--transition-offset)*-1)0;max-height:calc(100vh - 2rem);overflow-y:auto}.nv-dropdown-sub-content:popover-open{transform-origin:0}@media (prefers-reduced-motion:no-preference){.nv-dropdown-sub-content:popover-open{animation:submenu-in .25s var(--ease-out)}}.nv-dropdown-sub-content.\:popover-open{transform-origin:0}@media (prefers-reduced-motion:no-preference){.nv-dropdown-sub-content.\:popover-open{animation:submenu-in .25s var(--ease-out)}}@keyframes submenu-in{0%{translate:var(--submenu-translate-start);opacity:0}to{opacity:1;translate:0}}@keyframes submenu-out{0%{opacity:1;translate:0}to{translate:var(--submenu-translate-start);opacity:0}}.nv-flex{flex-direction:row;display:flex}.nv-flex--direction-row{flex-direction:row}.nv-flex--direction-row-reverse{flex-direction:row-reverse}.nv-flex--direction-col-reverse{flex-direction:column-reverse}.nv-flex--direction-col{flex-direction:column}.nv-flex--align-start{align-items:flex-start}.nv-flex--align-end{align-items:flex-end}.nv-flex--align-center{align-items:center}.nv-flex--align-baseline{align-items:baseline}.nv-flex--align-stretch{align-items:stretch}.nv-flex--justify-normal{justify-content:normal}.nv-flex--justify-start{justify-content:flex-start}.nv-flex--justify-end{justify-content:flex-end}.nv-flex--justify-center{justify-content:center}.nv-flex--justify-between{justify-content:space-between}.nv-flex--justify-around{justify-content:space-around}.nv-flex--justify-evenly{justify-content:space-evenly}.nv-flex--justify-stretch{justify-content:stretch}.nv-flex--justify-stretch>*{flex:1}.nv-flex--wrap-wrap{flex-wrap:wrap}.nv-flex--wrap-wrap-reverse{flex-wrap:wrap-reverse}.nv-flex--wrap-nowrap{flex-wrap:nowrap}.nv-form-field-root{gap:var(--spacing-density-xs);width:100%;font-family:var(--font-sans);line-height:var(--leading-lh-125);font-size:var(--text-12);--nv-form-field-label-group-width:160px;--nv-form-field-helper-margin-left:0px;flex-direction:column;display:flex;container-type:inline-size}@container (width>=320px){.nv-form-field-root.nv-form-field-root--label-position-left .nv-form-field-content-group{align-items:center;gap:calc(var(--spacing)*3);flex-direction:row}.nv-form-field-root.nv-form-field-root--label-position-left .nv-form-field-content-group .nv-form-field-label-group{width:var(--nv-form-field-label-group-width);justify-content:flex-start}.nv-form-field-root.nv-form-field-root--label-position-left .nv-form-field-helper{--nv-form-field-helper-margin-left:calc(var(--nv-form-field-label-group-width) + 12px)}}:is(.nv-form-field-root.nv-form-field-root--required,.nv-form-field-root:has(:required):not([data-required=false])) .nv-form-field-label-group .nv-label{padding-right:calc(var(--spacing)*3);position:relative}:is(.nv-form-field-root.nv-form-field-root--required,.nv-form-field-root:has(:required):not([data-required=false])) .nv-form-field-label-group .nv-label:after{font-weight:var(--font-weight-regular);right:calc(var(--spacing)*0);color:var(--text-color-feedback-danger-subtle);content:"*";position:absolute}.nv-form-field-label-group{align-items:center;gap:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*.5);flex-direction:row;flex-shrink:0;display:flex}.nv-form-field-label-group .nv-label{font-weight:var(--font-weight-bold);text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-12);overflow:hidden}.nv-form-field-label-group>svg,.nv-form-field-label-group>.nv-icon{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3);--icon-font-size:var(--text-12);flex-shrink:0}.nv-form-field-label-group>.nv-popover-trigger{cursor:pointer;padding:calc(var(--spacing)*0);color:var(--text-color-secondary);background-color:#0000;border:0;flex-shrink:0;align-items:center;display:flex}.nv-form-field-label-group>.nv-popover-trigger>.nv-icon{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3);--icon-font-size:var(--text-12)}.nv-form-field-content-group{gap:inherit;flex-direction:column;display:flex}.nv-form-field-content-group:has(.nv-input-shell)>.nv-form-field-label-group{justify-content:space-between}.nv-form-field-helper{padding-block:calc(var(--spacing)*.5);margin-left:var(--nv-form-field-helper-margin-left);color:var(--text-color-secondary);font-weight:var(--font-weight-regular)}.nv-form-field-helper.nv-form-field-helper--kind-error{color:var(--text-color-feedback-danger-subtle)}.nv-form-field-helper.nv-form-field-helper--kind-success{color:var(--text-color-feedback-success)}.nv-form-field-sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.nv-grid-item{grid-column-start:var(--nv-grid-item-col-start,auto);grid-column-end:var(--nv-grid-item-col-end,auto);grid-row-start:var(--nv-grid-item-row-start,auto);grid-row-end:var(--nv-grid-item-row-end,auto)}@media (width>=320px){.nv-grid-item{grid-column-start:var(--nv-grid-item-col-start-xs,var(--nv-grid-item-col-start,auto));grid-column-end:var(--nv-grid-item-col-end-xs,var(--nv-grid-item-col-end,auto));grid-row-start:var(--nv-grid-item-row-start-xs,var(--nv-grid-item-row-start,auto));grid-row-end:var(--nv-grid-item-row-end-xs,var(--nv-grid-item-row-end,auto))}}@media (width>=576px){.nv-grid-item{grid-column-start:var(--nv-grid-item-col-start-sm,var(--nv-grid-item-col-start-xs,var(--nv-grid-item-col-start,auto)));grid-column-end:var(--nv-grid-item-col-end-sm,var(--nv-grid-item-col-end-xs,var(--nv-grid-item-col-end,auto)));grid-row-start:var(--nv-grid-item-row-start-sm,var(--nv-grid-item-row-start-xs,var(--nv-grid-item-row-start,auto)));grid-row-end:var(--nv-grid-item-row-end-sm,var(--nv-grid-item-row-end-xs,var(--nv-grid-item-row-end,auto)))}}@media (width>=768px){.nv-grid-item{grid-column-start:var(--nv-grid-item-col-start-md,var(--nv-grid-item-col-start-sm,var(--nv-grid-item-col-start-xs,var(--nv-grid-item-col-start,auto))));grid-column-end:var(--nv-grid-item-col-end-md,var(--nv-grid-item-col-end-sm,var(--nv-grid-item-col-end-xs,var(--nv-grid-item-col-end,auto))));grid-row-start:var(--nv-grid-item-row-start-md,var(--nv-grid-item-row-start-sm,var(--nv-grid-item-row-start-xs,var(--nv-grid-item-row-start,auto))));grid-row-end:var(--nv-grid-item-row-end-md,var(--nv-grid-item-row-end-sm,var(--nv-grid-item-row-end-xs,var(--nv-grid-item-row-end,auto))))}}@media (width>=992px){.nv-grid-item{grid-column-start:var(--nv-grid-item-col-start-lg,var(--nv-grid-item-col-start-md,var(--nv-grid-item-col-start-sm,var(--nv-grid-item-col-start-xs,var(--nv-grid-item-col-start,auto)))));grid-column-end:var(--nv-grid-item-col-end-lg,var(--nv-grid-item-col-end-md,var(--nv-grid-item-col-end-sm,var(--nv-grid-item-col-end-xs,var(--nv-grid-item-col-end,auto)))));grid-row-start:var(--nv-grid-item-row-start-lg,var(--nv-grid-item-row-start-md,var(--nv-grid-item-row-start-sm,var(--nv-grid-item-row-start-xs,var(--nv-grid-item-row-start,auto)))));grid-row-end:var(--nv-grid-item-row-end-lg,var(--nv-grid-item-row-end-md,var(--nv-grid-item-row-end-sm,var(--nv-grid-item-row-end-xs,var(--nv-grid-item-row-end,auto)))))}}@media (width>=1200px){.nv-grid-item{grid-column-start:var(--nv-grid-item-col-start-xl,var(--nv-grid-item-col-start-lg,var(--nv-grid-item-col-start-md,var(--nv-grid-item-col-start-sm,var(--nv-grid-item-col-start-xs,var(--nv-grid-item-col-start,auto))))));grid-column-end:var(--nv-grid-item-col-end-xl,var(--nv-grid-item-col-end-lg,var(--nv-grid-item-col-end-md,var(--nv-grid-item-col-end-sm,var(--nv-grid-item-col-end-xs,var(--nv-grid-item-col-end,auto))))));grid-row-start:var(--nv-grid-item-row-start-xl,var(--nv-grid-item-row-start-lg,var(--nv-grid-item-row-start-md,var(--nv-grid-item-row-start-sm,var(--nv-grid-item-row-start-xs,var(--nv-grid-item-row-start,auto))))));grid-row-end:var(--nv-grid-item-row-end-xl,var(--nv-grid-item-row-end-lg,var(--nv-grid-item-row-end-md,var(--nv-grid-item-row-end-sm,var(--nv-grid-item-row-end-xs,var(--nv-grid-item-row-end,auto))))))}}@media (width>=1600px){.nv-grid-item{grid-column-start:var(--nv-grid-item-col-start-xxl,var(--nv-grid-item-col-start-xl,var(--nv-grid-item-col-start-lg,var(--nv-grid-item-col-start-md,var(--nv-grid-item-col-start-sm,var(--nv-grid-item-col-start-xs,var(--nv-grid-item-col-start,auto)))))));grid-column-end:var(--nv-grid-item-col-end-xxl,var(--nv-grid-item-col-end-xl,var(--nv-grid-item-col-end-lg,var(--nv-grid-item-col-end-md,var(--nv-grid-item-col-end-sm,var(--nv-grid-item-col-end-xs,var(--nv-grid-item-col-end,auto)))))));grid-row-start:var(--nv-grid-item-row-start-xxl,var(--nv-grid-item-row-start-xl,var(--nv-grid-item-row-start-lg,var(--nv-grid-item-row-start-md,var(--nv-grid-item-row-start-sm,var(--nv-grid-item-row-start-xs,var(--nv-grid-item-row-start,auto)))))));grid-row-end:var(--nv-grid-item-row-end-xxl,var(--nv-grid-item-row-end-xl,var(--nv-grid-item-row-end-lg,var(--nv-grid-item-row-end-md,var(--nv-grid-item-row-end-sm,var(--nv-grid-item-row-end-xs,var(--nv-grid-item-row-end,auto)))))))}}.nv-grid{width:100%;font-family:var(--font-sans);grid-template-columns:var(--nv-grid-template-columns,none);grid-template-rows:var(--nv-grid-template-rows,none);display:grid}.nv-grid--flow-row{grid-auto-flow:row}.nv-grid--flow-col{grid-auto-flow:column}.nv-grid--flow-dense{grid-auto-flow:dense}.nv-grid--flow-row-dense{grid-auto-flow:dense}.nv-grid--flow-col-dense{grid-auto-flow:column dense}@media (width>=320px){.nv-grid{grid-template-columns:var(--nv-grid-template-columns-xs,var(--nv-grid-template-columns,none));grid-template-rows:var(--nv-grid-template-rows-xs,var(--nv-grid-template-rows,none))}}@media (width>=576px){.nv-grid{grid-template-columns:var(--nv-grid-template-columns-sm,var(--nv-grid-template-columns-xs,var(--nv-grid-template-columns,none)));grid-template-rows:var(--nv-grid-template-rows-sm,var(--nv-grid-template-rows-xs,var(--nv-grid-template-rows,none)))}}@media (width>=768px){.nv-grid{grid-template-columns:var(--nv-grid-template-columns-md,var(--nv-grid-template-columns-sm,var(--nv-grid-template-columns-xs,var(--nv-grid-template-columns,none))));grid-template-rows:var(--nv-grid-template-rows-md,var(--nv-grid-template-rows-sm,var(--nv-grid-template-rows-xs,var(--nv-grid-template-rows,none))))}}@media (width>=992px){.nv-grid{grid-template-columns:var(--nv-grid-template-columns-lg,var(--nv-grid-template-columns-md,var(--nv-grid-template-columns-sm,var(--nv-grid-template-columns-xs,var(--nv-grid-template-columns,none)))));grid-template-rows:var(--nv-grid-template-rows-lg,var(--nv-grid-template-rows-md,var(--nv-grid-template-rows-sm,var(--nv-grid-template-rows-xs,var(--nv-grid-template-rows,none)))))}}@media (width>=1200px){.nv-grid{grid-template-columns:var(--nv-grid-template-columns-xl,var(--nv-grid-template-columns-lg,var(--nv-grid-template-columns-md,var(--nv-grid-template-columns-sm,var(--nv-grid-template-columns-xs,var(--nv-grid-template-columns,none))))));grid-template-rows:var(--nv-grid-template-rows-xl,var(--nv-grid-template-rows-lg,var(--nv-grid-template-rows-md,var(--nv-grid-template-rows-sm,var(--nv-grid-template-rows-xs,var(--nv-grid-template-rows,none))))))}}@media (width>=1600px){.nv-grid{grid-template-columns:var(--nv-grid-template-columns-xxl,var(--nv-grid-template-columns-xl,var(--nv-grid-template-columns-lg,var(--nv-grid-template-columns-md,var(--nv-grid-template-columns-sm,var(--nv-grid-template-columns-xs,var(--nv-grid-template-columns,none)))))));grid-template-rows:var(--nv-grid-template-rows-xxl,var(--nv-grid-template-rows-xl,var(--nv-grid-template-rows-lg,var(--nv-grid-template-rows-md,var(--nv-grid-template-rows-sm,var(--nv-grid-template-rows-xs,var(--nv-grid-template-rows,none)))))))}}.nv-group{align-items:stretch;width:fit-content;height:fit-content;display:inline-flex}.nv-group,.nv-group.nv-group--kind-flush{--group-item-overlap:1px}.nv-group.nv-group--kind-gap{--group-item-overlap:0;gap:1px}.nv-group.nv-group--kind-border>*:where(:not(:first-child)){border-block-width:0;border-right-width:0;border-left-width:var(--border-width-1);border-color:var(--border-color-base)}.nv-group.nv-group>*{max-height:none;height:auto!important}.nv-group.nv-group>*:where(:not(:first-child)){margin-block-start:0;margin-inline-start:calc(var(--group-item-overlap)*-1)}.nv-group.nv-group>*:focus-within{z-index:1}.nv-group.nv-group:has(>:not([popover])~:not([popover]))>:not([popover]):where(:first-child){border-top-right-radius:var(--radius-none)!important;border-bottom-right-radius:var(--radius-none)!important}.nv-group.nv-group:has(>:not([popover])~:not([popover]))>:not([popover]):where(:not(:has(~:not([popover])))){border-top-left-radius:var(--radius-none)!important;border-bottom-left-radius:var(--radius-none)!important}.nv-group.nv-group:has(>:not([popover])~:not([popover]))>:not([popover]):where(:not(:first-child):has(~:not([popover]))){border-radius:var(--radius-none)!important}.nv-hero-root{--padding:calc(var(--spacing)*8)calc(var(--spacing)*6);--max-width:940px;--text-color:var(--text-color-primary);isolation:isolate;width:100%;height:fit-content;font-family:var(--font-sans);padding:var(--padding);color:var(--text-color);font-style:normal;font-weight:var(--font-weight-regular);display:flex;position:relative;overflow:hidden;container-type:inline-size}.nv-hero-content{width:clamp(100.0%,var(--max-width),100.0%);max-width:var(--max-width);gap:calc(var(--spacing)*6);flex-direction:column;margin-inline:auto;display:flex;position:relative}@container (width>=48rem){.nv-hero-content{padding-block:calc(var(--spacing)*16)}}@container (width>=64rem){.nv-hero-content{padding-block:calc(var(--spacing)*31)}}.nv-hero-media{inset:calc(var(--spacing)*0);object-fit:cover;width:100%;height:100%;position:absolute}.nv-hero-subheading{-webkit-line-clamp:1;font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold);text-wrap:pretty;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}@container (width>=48rem){.nv-hero-subheading{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}}@container (width>=64rem){.nv-hero-subheading{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}}.nv-hero-heading{-webkit-line-clamp:2;font-family:var(--font-sans);font-size:var(--text-44);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold);text-wrap:pretty;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}@container (width>=48rem){.nv-hero-heading{font-family:var(--font-sans);font-size:var(--text-44);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}}@container (width>=64rem){.nv-hero-heading{font-family:var(--font-sans);font-size:var(--text-50);line-height:1.24;font-weight:var(--font-weight-bold)}}.nv-hero-body{-webkit-line-clamp:3;font-size:var(--text-24);line-height:var(--leading-lh-175);text-wrap:pretty;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.nv-hero-footer{gap:calc(var(--spacing)*2);padding-top:calc(var(--spacing)*2);display:inline-flex}.nv-horizontal-nav-list{isolation:isolate;display:flex}.nv-horizontal-nav-item{--nav-padding-x:1rem;--nav-transition-duration:.2s;--nav-edge-offset:calc(100.0% - var(--nav-padding-x));cursor:pointer;align-items:center;gap:calc(var(--spacing)*2);height:32px;padding-inline:calc(var(--spacing)*4);font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-regular);white-space:nowrap;color:var(--text-color-secondary);display:flex;position:relative}.nv-horizontal-nav-item:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px;border-radius:4px}@media (prefers-reduced-motion:no-preference){.nv-horizontal-nav-item{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.2s;transition-timing-function:var(--ease-out)}}.nv-horizontal-nav-item:before,.nv-horizontal-nav-item:after{content:"";pointer-events:none;bottom:calc(var(--spacing)*0);z-index:10;border-bottom:2px solid #0000;position:absolute}.nv-horizontal-nav-item:before{left:var(--nav-padding-x);right:var(--nav-edge-offset)}.nv-horizontal-nav-item:after{left:var(--nav-edge-offset);right:var(--nav-padding-x)}@media (hover:hover){.nv-horizontal-nav-item:not(:disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,[data-active]):where(:hover,[data-hover]){color:var(--text-color-secondary)}.nv-horizontal-nav-item:not(:disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,[data-active]):where(:hover,[data-hover]):before,.nv-horizontal-nav-item:not(:disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,[data-active]):where(:hover,[data-hover]):after{border-bottom-color:var(--border-color-interaction-hover);left:var(--nav-padding-x);right:var(--nav-padding-x)}}.nv-horizontal-nav-item:disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--disabled{cursor:not-allowed;color:var(--text-color-disabled)}.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,.nv-horizontal-nav-item[data-active]{color:var(--text-color-primary);font-weight:var(--font-weight-bold)}:is(.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,.nv-horizontal-nav-item[data-active]):before,:is(.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,.nv-horizontal-nav-item[data-active]):after{border-bottom-color:var(--border-color-interaction-selected);left:var(--nav-padding-x);right:var(--nav-padding-x)}@media (prefers-reduced-motion:no-preference){.nv-horizontal-nav-item:before,.nv-horizontal-nav-item:after{transition:left var(--nav-transition-duration)var(--ease-out),right var(--nav-transition-duration)var(--ease-out),border-color var(--nav-transition-duration)var(--ease-out)}.nv-horizontal-nav-item:before{transition-duration:0s,0s,var(--nav-transition-duration)}.nv-horizontal-nav-item:not(:disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,[data-active]):where(:hover,[data-hover]):before{transition-duration:var(--nav-transition-duration),var(--nav-transition-duration),var(--nav-transition-duration)}.nv-horizontal-nav-item:not(:disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--disabled,.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,[data-active]):where(:hover,[data-hover]):after{transition-delay:var(--nav-transition-duration),var(--nav-transition-duration),0s;transition-duration:0s,0s,var(--nav-transition-duration)}:is(.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,.nv-horizontal-nav-item[data-active]):before,:is(.nv-horizontal-nav-item.nv-horizontal-nav-item--selected,.nv-horizontal-nav-item[data-active]):after{transition-duration:0s,0s,var(--nav-transition-duration)}}.nv-input-shell textarea,.nv-input-shell input{font:inherit;letter-spacing:inherit;word-spacing:inherit}.nv-input-shell{--nv-input-height:40px;--nv-input-padding:12px;height:var(--nv-input-height);--input-gap:6px;align-items:center;gap:var(--input-gap);border-radius:var(--radius-md);width:100%;padding-inline:var(--nv-input-padding);font:var(--font-sans);font-size:var(--text-14);font-style:normal;font-weight:var(--font-weight-regular);color:var(--text-color-primary);outline-offset:var(--outline-offset,-2px);outline:2px solid #0000;display:flex}.nv-input-shell select,.nv-input-shell [data-input-slot]{cursor:pointer}.nv-input-shell:where(:not(.nv-input-shell--kind-floating)){border:1px solid var(--border-color-interaction-base);background:var(--background-color-interaction-base)}:where(.nv-input-shell svg,.nv-input-shell .nv-icon):not(.nv-button svg,.nv-button .nv-icon){--icon-font-size:var(--text-16);width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);color:var(--text-color-base);flex-shrink:0}@media (hover:hover){.nv-input-shell:hover{border-color:var(--border-color-interaction-hover)}.nv-input-shell:hover:has(input[type=file]){background-color:var(--background-color-interaction-hover)}}.nv-input-shell:where(:has([data-status=error])){--border-color:var(--border-color-feedback-danger);border-color:var(--border-color-feedback-danger)}.nv-input-shell.nv-input-shell--validated:has(:user-invalid){--border-color:var(--border-color-feedback-danger);border-color:var(--border-color-feedback-danger)}.nv-input-shell:where(:has([data-status=success])){--border-color:var(--border-color-feedback-success);border-color:var(--border-color-feedback-success)}.nv-input-shell.nv-input-shell--validated:has(:user-valid){--border-color:var(--border-color-feedback-success);border-color:var(--border-color-feedback-success)}.nv-input-shell[data-state=open],.nv-input-shell:has([data-state=open]),.nv-input-shell:has(:focus-visible){border-color:var(--border-color-interaction-selected)}.nv-input-shell:has(select:open){border-color:var(--border-color-interaction-selected)}.nv-input-shell:has(:focus-visible){outline-color:var(--outline-color,var(--border-color,currentColor))}.nv-input-shell:has([readonly]){background-color:#0000;border-color:#0000}.nv-input-shell:has([readonly])>.nv-dismiss-button{display:none}.nv-input-shell:where(:has([data-disabled=true]),[data-disabled=true]),.nv-input-shell:has(input:disabled,textarea:disabled,select:disabled,[data-input-slot]:disabled:not([readonly]),[data-input-slot][aria-disabled=true]:not([readonly])){cursor:not-allowed;border-color:var(--border-color-interaction-disabled);background-color:var(--background-color-interaction-disabled);color:var(--text-color-disabled)}:is(.nv-input-shell:where(:has([data-disabled=true]),[data-disabled=true]),.nv-input-shell:has(input:disabled,textarea:disabled,select:disabled,[data-input-slot]:disabled:not([readonly]),[data-input-slot][aria-disabled=true]:not([readonly]))) ::file-selector-button{-webkit-text-decoration-color:var(--border-color-interaction-disabled)!important;text-decoration-color:var(--border-color-interaction-disabled)!important}:is(.nv-input-shell:where(:has([data-disabled=true]),[data-disabled=true]),.nv-input-shell:has(input:disabled,textarea:disabled,select:disabled,[data-input-slot]:disabled:not([readonly]),[data-input-slot][aria-disabled=true]:not([readonly])))>.nv-dismiss-button{display:none}.nv-input-shell input,.nv-input-shell textarea,.nv-input-shell select,.nv-input-shell>[data-input-slot]{appearance:none;text-align:left;background-color:#0000;border:none;outline:none;width:100%;height:100%}:is(.nv-input-shell input,.nv-input-shell textarea,.nv-input-shell select,.nv-input-shell>[data-input-slot])::placeholder,:is(.nv-input-shell input,.nv-input-shell textarea,.nv-input-shell select,.nv-input-shell>[data-input-slot])[data-has-selected-value=false]{color:var(--text-color-placeholder)}.nv-input-shell>select:invalid{color:var(--text-color-placeholder)}:is(.nv-input-shell:has(:placeholder-shown),.nv-input-shell:has([data-has-selected-value=false]))>.nv-dismiss-button{display:none}.nv-input-shell:has(.nv-dismiss-button) input[type=search]::-webkit-search-cancel-button{appearance:none;display:none}.nv-input-shell:has(textarea){height:auto;padding-block:8px}.nv-input-shell:has(textarea) textarea{scrollbar-width:thin;scrollbar-color:var(--nv-scrollbar-color);min-height:3lh}.nv-input-shell:has(input[type=file]:not([data-hidden=true])){cursor:pointer;height:auto;padding-block:24px}.nv-input-shell:has(input[type=file]:not([data-hidden=true])) input[type=file]{width:auto;margin:0 auto}.nv-input-shell:has(input[type=file]:not([data-hidden=true])) ::file-selector-button{text-decoration:underline;-webkit-text-decoration-color:var(--border-color-brand);text-decoration-color:var(--border-color-brand);text-underline-offset:4px;background:0 0;border:0}.nv-input-shell:has(button[data-input-slot]){gap:0;padding-inline:0}.nv-input-shell:has(button[data-input-slot]) button[data-input-slot]{padding-inline:var(--input-gap);line-height:1}.nv-input-shell:has(button[data-input-slot]) button[data-input-slot]:first-child{padding-inline-start:var(--nv-input-padding)}.nv-input-shell:has(button[data-input-slot]) button[data-input-slot]:last-child{padding-inline-end:var(--nv-input-padding)}.nv-input-shell:has(button[data-input-slot]):has(button[data-input-slot]:not(:first-child)){padding-inline-start:var(--nv-input-padding)}.nv-input-shell:has(button[data-input-slot])>:last-child:not(button[data-input-slot]){padding-inline-end:var(--nv-input-padding)}.nv-input-shell>.nv-dismiss-button{--nv-button-icon-margin:-4px;--nv-button-icon-color:var(--text-color-base)}.nv-input-shell>button[data-input-slot],.nv-input-shell:is(button){text-overflow:ellipsis;white-space:nowrap;overflow:hidden}@media (prefers-reduced-motion:no-preference){.nv-input-shell{transition-property:border-color,color,background-color,outline-color;transition-duration:.25s;transition-timing-function:var(--ease-out)}}@media (scripting:none){.nv-input-shell .nv-dismiss-button{display:none}}.nv-input-shell.nv-input-shell--size-small{--nv-input-padding:8px;--nv-input-height:28px;font-size:var(--text-12);padding-block:6px}:where(.nv-input-shell.nv-input-shell--size-small svg,.nv-input-shell.nv-input-shell--size-small .nv-icon):not(.nv-button svg,.nv-button .nv-icon){--icon-font-size:var(--text-12);width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.nv-input-shell.nv-input-shell--size-large{--nv-input-padding:16px;--nv-input-height:48px;font-size:var(--text-16);padding-block:12px}.nv-input-shell.nv-input-shell--layout-vertical{--nv-input-height:auto;padding-block:var(--nv-input-padding,12px);flex-direction:column}.nv-input-shell input:is([type=time],[type=date],[type=datetime-local]){font-variant-numeric:tabular-nums}.nv-input-shell input:is([type=time],[type=date],[type=datetime-local]):not(.nv-date-picker-native-fallback)::-webkit-calendar-picker-indicator{display:none}.nv-input-shell ::-webkit-datetime-edit-hour-field:focus{background:var(--background-color-interaction-hover);border-radius:var(--spacing)}.nv-input-shell ::-webkit-datetime-edit-minute-field:focus{background:var(--background-color-interaction-hover);border-radius:var(--spacing)}.nv-input-shell ::-webkit-datetime-edit-second-field:focus{background:var(--background-color-interaction-hover);border-radius:var(--spacing)}.nv-input-shell ::-webkit-datetime-edit-ampm-field:focus{background:var(--background-color-interaction-hover);border-radius:var(--spacing)}.nv-input-shell ::-webkit-datetime-edit-year-field:focus{background:var(--background-color-interaction-hover);border-radius:var(--spacing)}.nv-input-shell ::-webkit-datetime-edit-month-field:focus{background:var(--background-color-interaction-hover);border-radius:var(--spacing)}.nv-input-shell ::-webkit-datetime-edit-day-field:focus{background:var(--background-color-interaction-hover);border-radius:var(--spacing)}.nv-input-shell-control{flex-shrink:0;align-items:center;height:100%;display:flex}.nv-label{width:fit-content;font-family:var(--font-sans);color:var(--text-color-primary);font-weight:var(--nv-label-font-weight,var(--font-weight-regular));font-size:var(--nv-label-font-size,var(--text-14));line-height:var(--nv-label-line-height,var(--leading-lh-125));text-overflow:var(--nv-label-text-overflow,clip);overflow:var(--nv-label-overflow,visible);vertical-align:middle}.nv-label--disabled{cursor:not-allowed;color:var(--text-color-disabled)}.nv-label--size-small{font-size:var(--nv-label-font-size,var(--text-12))}.nv-label--size-medium{font-size:var(--nv-label-font-size,var(--text-14))}.nv-label--size-large{font-size:var(--nv-label-font-size,var(--text-16))}.nv-label--required{padding-right:calc(var(--spacing)*3);position:relative}.nv-label--required:after{font-weight:var(--font-weight-regular);right:calc(var(--spacing)*0);color:var(--text-color-feedback-danger-subtle);content:"*";position:absolute}.nv-list-root{margin:calc(var(--spacing)*0);gap:calc(var(--spacing)*1);width:100%;padding:calc(var(--spacing)*0);font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);color:var(--text-color-primary);flex-direction:column;list-style-type:none;display:flex}.nv-list-item{align-items:flex-start;gap:calc(var(--spacing)*1);width:100%;display:flex}.nv-list-item-marker{font-weight:var(--font-weight-bold)}.nv-list-root--kind-ordered .nv-list-item-marker{justify-content:flex-end}.nv-list-item-marker,.nv-list-item-marker svg,.nv-list-item-marker .nv-icon{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);flex-shrink:0;justify-content:center;align-items:center;display:flex}.nv-menu-root{font-family:var(--font-sans);font-size:var(--text-14);color:var(--text-color-primary);font-weight:var(--font-weight-regular);z-index:1060;align-items:flex-start;gap:calc(var(--spacing)*0);border-radius:var(--radius-md);border:1px solid;border-color:var(--border-color-base);background-color:var(--background-color-surface-raised);box-shadow:var(--shadow-md);touch-action:pan-y;scrollbar-width:thin;scrollbar-color:var(--nv-scrollbar-color);--transition-offset:12px;--menu-translate-start:0 calc(var(--transition-offset)*-1);flex-direction:column;font-style:normal;line-height:1.14286;display:flex;overflow-y:auto}.nv-menu-root.nv-menu--filterable:not(:has(.nv-menu-item)):after{padding:var(--spacing-density-lg);color:var(--text-color-placeholder);content:attr(data-empty-message,"No results found")}.nv-menu-root.nv-menu--filterable:not(:has(.nv-menu-item)) .nv-divider-root{display:none}.nv-menu-list{margin:calc(var(--spacing)*0);align-items:flex-start;gap:calc(var(--spacing)*0);width:100%;padding:calc(var(--spacing)*0);flex-direction:column;list-style-type:none;display:flex}.nv-menu-section{align-items:center;gap:calc(var(--spacing)*0);border-bottom:1px solid;border-bottom-color:var(--border-color-base);background-color:var(--background-color-surface-raised);flex-direction:column;width:100%;display:flex}.nv-menu-section:last-child{border:none}.nv-menu-section:not(:has(.nv-menu-item)){display:none}.nv-menu-heading{font-size:var(--text-14);font-weight:var(--font-weight-bold);align-items:flex-start;gap:calc(var(--spacing)*1.5);text-overflow:ellipsis;white-space:nowrap;width:100%;padding:var(--spacing-density-lg);line-height:1.14286;display:flex;overflow:hidden}.nv-menu-search.nv-input-shell{margin:calc(var(--spacing)*2);width:calc(100.0% - var(--spacing)*4)}.nv-menu-search.nv-input-shell input[type=search]::-webkit-search-decoration{-webkit-appearance:none;display:none}.nv-menu-search.nv-input-shell input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;display:none}.nv-menu-root>li[role=none],.nv-menu-list>li[role=none],.nv-menu-section>li[role=none]{width:100%;list-style-type:none}.nv-menu-item{cursor:pointer;align-items:center;gap:calc(var(--spacing)*1.5);width:100%;padding:var(--spacing-density-lg);text-align:left;outline-offset:-2px;background-color:#0000;border:none;flex-shrink:0;list-style-type:none;display:flex}.nv-menu-item :where(svg,.nv-icon){color:var(--text-color-base)}@media (prefers-reduced-motion:no-preference){.nv-menu-item{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke;transition-duration:.25s;transition-timing-function:var(--ease-out)}}.nv-menu-item.nv-menu-checkbox-item,.nv-menu-item.nv-menu-radio-group-item{gap:calc(var(--spacing)*2)}.nv-menu-item.nv-menu-checkbox-item:has(input[role=menuitemcheckbox]:focus-visible),.nv-menu-item.nv-menu-radio-group-item:has(input[role=menuitemradio]:focus-visible){outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}.nv-menu-item:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor)}.nv-menu-item.nv-menu-checkbox-item input.nv-checkbox-input:focus-visible,.nv-menu-item.nv-menu-radio-group-item input[role=menuitemradio]:focus-visible{outline:none}.nv-menu-item:hover{background-color:var(--background-color-interaction-hover)}.nv-menu-item:active,.nv-menu-item[data-active-item]{background-color:var(--background-color-interaction-pressed)}.nv-menu-item.nv-menu-item--danger{color:var(--text-color-feedback-danger)}.nv-menu-item.nv-menu-item--danger :where(svg,.nv-icon){color:var(--text-color-feedback-danger)}.nv-menu-item.nv-menu-item--danger:hover{background-color:var(--background-color-feedback-danger-subtle-hover);color:var(--text-color-feedback-danger-subtle)}.nv-menu-item.nv-menu-item--danger:active{background-color:var(--background-color-feedback-danger-subtle-pressed);color:var(--text-color-feedback-danger-strong)}.nv-menu-item .nv-menu-item-slot{justify-content:center;align-items:center;gap:calc(var(--spacing)*1.5);flex-shrink:0;min-width:1em;display:flex}.nv-menu-item svg,.nv-menu-item .nv-icon{flex-shrink:0}.nv-menu-item.nv-menu-item--disabled,.nv-menu-item[data-disabled]{cursor:not-allowed;background-color:var(--background-color-interaction-disabled);color:var(--text-color-disabled)}:is(.nv-menu-item.nv-menu-item--disabled,.nv-menu-item[data-disabled]) :where(svg,.nv-icon){color:var(--text-color-disabled)}:is(.nv-menu-item.nv-menu-item--disabled,.nv-menu-item[data-disabled]):hover{background-color:var(--background-color-interaction-disabled);color:var(--text-color-disabled)}.nv-menu-item[data-state=unchecked] [data-state-indicator]{display:none}.nv-menu-radio-group{margin:calc(var(--spacing)*0);min-width:calc(var(--spacing)*0);padding:calc(var(--spacing)*0);border-top:0;border-left:0;border-right:0}.nv-menu-radio-group.nv-radio-group-root{align-items:stretch;gap:calc(var(--spacing)*0);width:100%}.nv-menu-item.nv-radio-group-item{width:100%}.nv-menu-radio-group>li[role=none]{width:100%;list-style-type:none;display:flex}.nv-menu-item-label{min-height:calc(var(--spacing)*4);text-overflow:ellipsis;white-space:nowrap;text-align:left;align-content:center;width:100%;display:inline-block;overflow:hidden}dialog.nv-modal-overlay,dialog.nv-modal-dialog{margin:calc(var(--spacing)*0);max-width:none;max-height:none;padding:calc(var(--spacing)*0);background-color:#0000;border:none}:is(dialog.nv-modal-overlay,dialog.nv-modal-dialog)[open]{inset:calc(var(--spacing)*0);z-index:1000;position:fixed}:is(dialog.nv-modal-overlay,dialog.nv-modal-dialog):not([open]){display:none}:is(dialog.nv-modal-overlay,dialog.nv-modal-dialog) .nv-modal-content{position:relative;inset:auto;translate:0}dialog.nv-modal-overlay[open]{justify-content:center;align-items:center;width:100vw;height:100vh;display:flex}dialog.nv-modal-overlay::backdrop{background-color:var(--background-color-surface-blanket)}@media (prefers-reduced-motion:no-preference){dialog.nv-modal-overlay{animation:modal-in .3s var(--ease-out)}dialog.nv-modal-overlay[data-state=closed]{animation:modal-out .2s var(--ease-out)forwards}dialog.nv-modal-overlay::backdrop{animation:modal-in .3s var(--ease-out)}dialog.nv-modal-overlay[data-state=closed]::backdrop{animation:modal-out .2s var(--ease-out)forwards}}dialog.nv-modal-dialog[open]{justify-content:center;align-items:center;width:100vw;height:100vh;display:flex}dialog.nv-modal-dialog::backdrop{background-color:#0000}.nv-modal-overlay[popover]{margin:calc(var(--spacing)*0);max-width:none;max-height:none;padding:calc(var(--spacing)*0);background-color:#0000;border:none}.nv-modal-overlay[popover]:popover-open{inset:calc(var(--spacing)*0);z-index:1000;justify-content:center;align-items:center;width:100vw;height:100vh;display:flex;position:fixed}.nv-modal-overlay[popover]::backdrop{background-color:var(--background-color-surface-blanket)}.nv-modal-overlay[popover] .nv-modal-content{position:relative;inset:auto;translate:0}.nv-modal-content{border:1px solid var(--border-color-base);z-index:1050;border-radius:var(--radius-xl);background-color:var(--background-color-surface-overlay);width:420px;max-width:95%;max-height:90dvh;font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);color:var(--text-color-primary);gap:var(--spacing-density-2xl);padding:var(--spacing-density-2xl);flex-direction:column;font-style:normal;display:flex;position:relative}.nv-modal-heading{align-items:center;gap:calc(var(--spacing)*2);width:100%;padding-right:calc(var(--spacing)*8);font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-bold);line-height:var(--leading-lh-100);display:flex;position:relative}.nv-modal-heading>svg,.nv-modal-heading>.nv-icon{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);color:var(--text-color-base);flex-shrink:0}.nv-modal-heading.nv-modal-heading--hidden{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.nv-modal-heading.nv-modal-heading--invisible{visibility:hidden}.nv-modal-main{min-height:calc(var(--spacing)*0);gap:calc(var(--spacing)*4);scrollbar-width:thin;scrollbar-color:var(--nv-scrollbar-color);flex-direction:column;margin:-4px;padding:4px;display:flex;overflow-y:auto}.nv-modal-footer{justify-content:flex-end;align-items:center;gap:calc(var(--spacing)*2);margin-top:auto;display:flex}.nv-modal-close{top:var(--spacing-density-2xl);right:var(--spacing-density-lg);position:absolute;translate:0 -25%}.nv-modal-close>:not(svg):not(.nv-icon){clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.nv-modal-portal{pointer-events:none;inset:calc(var(--spacing)*0);z-index:1060;position:fixed}.nv-modal-portal>*{pointer-events:auto}.nv-notification-root{border-radius:var(--radius-lg);border-left:4px solid;border-left-color:var(--border-color-feedback-info);background-color:var(--background-color-surface-overlay);width:100%;padding:calc(var(--spacing)*4);font-family:var(--font-sans);color:var(--text-color-primary);box-shadow:var(--shadow-lg);position:relative}.nv-notification-root.nv-notification-root--status-error{border-left-color:var(--border-color-feedback-danger)}.nv-notification-root.nv-notification-root--status-error .nv-notification-icon{color:var(--text-color-feedback-danger)}.nv-notification-root.nv-notification-root--status-success{border-left-color:var(--border-color-feedback-success)}.nv-notification-root.nv-notification-root--status-success .nv-notification-icon{color:var(--text-color-feedback-success)}.nv-notification-root.nv-notification-root--status-warning{border-left-color:var(--border-color-feedback-warning)}.nv-notification-root.nv-notification-root--status-warning .nv-notification-icon{color:var(--text-color-feedback-warning)}.nv-notification-root.nv-notification-root--kind-inline .nv-notification-close-button-section{align-content:center;position:static}.nv-notification-root.nv-notification-root--kind-inline .nv-notification-close-button-section button{margin-right:calc(var(--spacing)*-3)}.nv-notification-root.nv-notification-root--kind-inline .nv-notification-content{gap:calc(var(--spacing)*3);grid-template-rows:repeat(1,minmax(0,1fr));grid-template-columns:auto 1fr auto auto;grid-template-areas:"icon header footer close-button"}.nv-notification-root.nv-notification-root--kind-inline .nv-notification-content:not(:has(.nv-notification-footer)){grid-template-columns:auto 1fr auto;grid-template-areas:"icon header close-button"}.nv-notification-root.nv-notification-root--kind-inline .nv-notification-content:not(:has(.nv-notification-close-button-section)){grid-template-columns:auto 1fr auto;grid-template-areas:"icon header footer"}.nv-notification-root.nv-notification-root--kind-inline .nv-notification-footer{align-items:center}.nv-notification-root .nv-notification-content{gap:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*0);grid-template:"icon header""footer footer"/auto 1fr;display:grid}.nv-notification-root .nv-notification-content:has(.nv-notification-footer){row-gap:calc(var(--spacing)*3)}.nv-notification-root .nv-notification-close-button-section{top:calc(var(--spacing)*1);right:calc(var(--spacing)*1);grid-area:close-button;position:absolute}.nv-notification-root .nv-notification-icon{color:var(--text-color-feedback-info);grid-area:icon}.nv-notification-root .nv-notification-header{gap:calc(var(--spacing)*2);flex-direction:column;grid-area:header;display:flex}.nv-notification-root .nv-notification-heading{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-notification-root .nv-notification-subheading{font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-notification-root .nv-notification-footer{justify-content:flex-end;gap:calc(var(--spacing)*2);grid-area:footer;display:flex}.nv-notification-root .nv-notification-icon{padding-block:calc(var(--spacing)*1)}.nv-page-header-root{width:100%;container-type:inline-size}.nv-page-header-container{font-family:var(--font-sans);color:var(--text-color-primary);font-style:normal;font-weight:var(--font-weight-regular);gap:calc(var(--spacing)*4);flex-direction:column;width:100%;display:flex}@container (width>=42rem){.nv-page-header-container{flex-direction:row}}.nv-page-header-container .nv-page-header-content{gap:var(--spacing-density-xl);flex-direction:column;flex:1;display:flex}.nv-page-header-container .nv-page-header-header{gap:calc(var(--spacing)*2);flex-direction:column;display:flex}.nv-page-header-container .nv-page-header-subheading{font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-light);margin-block:-2px}.nv-page-header-container .nv-page-header-heading{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-page-header-container .nv-page-header-description{font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-page-header-container .nv-page-header-footer{justify-content:flex-end;align-items:flex-end;gap:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}@container (width>=42rem){.nv-page-header-container .nv-page-header-footer{flex-wrap:nowrap}}@container (width<42rem){.nv-page-header-container .nv-page-header-footer>*{flex:1}}.nv-page-header-container.nv-page-header-container--kind-floating{border-radius:var(--radius-xl);border:1px solid;border-color:var(--border-color-base);background-color:var(--background-color-surface-base);padding:var(--spacing-density-2xl);box-shadow:var(--shadow-md)}.nv-pagination-root{align-items:center;gap:calc(var(--spacing)*2);width:100%;font-size:var(--text-14);flex-wrap:wrap;display:flex;overflow:hidden;container-type:inline-size}.nv-pagination-page-size-select{margin-inline:calc(var(--spacing)*1);color:var(--text-color-primary);width:72px!important}.nv-pagination-arrow-button{flex-shrink:0}.nv-pagination-page-input{flex-shrink:0;width:calc(var(--spacing)*16)!important}.nv-pagination-page-list{width:fit-content!important}.nv-pagination-page-list .nv-tabs-list{border-bottom:none}.nv-pagination-page-count-text{white-space:nowrap;color:var(--text-color-secondary);font-weight:var(--font-weight-regular);flex-shrink:0}.nv-pagination-divider{align-self:stretch;height:auto}.nv-pagination-divider .nv-divider-element{height:100%;min-height:calc(var(--spacing)*0)}.nv-pagination-item-range-text{text-overflow:ellipsis;white-space:nowrap;color:var(--text-color-secondary);font-weight:var(--font-weight-regular);overflow:hidden}.nv-pagination-controls-group{white-space:nowrap;color:var(--text-color-secondary);font-weight:var(--font-weight-regular);flex-grow:1;flex-shrink:1;justify-content:flex-start;align-items:center;display:flex;overflow:hidden}.nv-pagination-controls-group .nv-divider-root{margin-right:calc(var(--spacing)*3.5);flex-grow:0}.nv-pagination-navigation-group{flex-shrink:0;align-items:center;display:flex}.nv-pagination--kind-input{justify-content:flex-start;gap:calc(var(--spacing)*2)}.nv-pagination--kind-input:has(.nv-pagination-page-size-select) .nv-pagination-navigation-group{margin-inline:auto}.nv-pagination--kind-input .nv-pagination-navigation-group{gap:calc(var(--spacing)*2)}@container (width<=720px){.nv-pagination--kind-input .nv-pagination-controls-group{justify-content:center;width:100%}}.nv-pagination--kind-input:not(:has(.nv-pagination-page-size-select)){justify-content:center}.nv-pagination--kind-tabs{align-items:center;gap:calc(var(--spacing)*2);grid-template-columns:1fr auto 1fr;width:100%;display:grid}.nv-pagination--kind-tabs .nv-pagination-controls-group--start{grid-column-start:1;justify-content:center;justify-self:flex-start;align-items:center;display:flex}.nv-pagination--kind-tabs .nv-pagination-controls-group--start .nv-divider-root{margin-right:calc(var(--spacing)*0)!important}@container (width<=720px){.nv-pagination--kind-tabs .nv-pagination-controls-group--start{grid-column:1/-1;justify-self:center;width:100%}.nv-pagination--kind-tabs .nv-pagination-controls-group--start .nv-divider-root{display:none}}.nv-pagination--kind-tabs .nv-pagination-navigation-group--tabs{grid-column-start:2;justify-self:center;align-items:center;display:flex}@container (width<=720px){.nv-pagination--kind-tabs .nv-pagination-navigation-group--tabs{grid-column:1/-1}}.nv-pagination--kind-tabs .nv-pagination-navigation-group{gap:calc(var(--spacing)*0)}.nv-pagination--kind-tabs .nv-pagination-controls-group--end{grid-column-start:3;justify-self:flex-end;align-items:center;display:flex}.nv-pagination--kind-tabs .nv-pagination-controls-group--end .nv-pagination-page-input{margin-right:calc(var(--spacing)*2)}@container (width<=720px){.nv-pagination--kind-tabs .nv-pagination-controls-group--end{grid-column:1/-1;justify-content:center;width:100%}.nv-pagination--kind-tabs .nv-pagination-controls-group--end .nv-divider-root{display:none}}.nv-pagination--kind-tabs:not(:has(.nv-pagination-page-size-select)) .nv-pagination-navigation-group--tabs{grid-column:1/-1}.nv-pagination--kind-simple{justify-content:flex-start;gap:calc(var(--spacing)*2)}.nv-pagination--kind-simple:has(.nv-pagination-page-size-select) .nv-pagination-navigation-group{margin-inline:auto}.nv-pagination--kind-simple .nv-pagination-navigation-group{gap:calc(var(--spacing)*2)}@container (width<=720px){.nv-pagination--kind-simple .nv-pagination-controls-group{justify-content:center;width:100%}}.nv-pagination--kind-simple:not(:has(.nv-pagination-page-size-select)){justify-content:center}.nv-panel-root{border:1px solid;border-color:var(--border-color-base);background-color:var(--background-color-surface-base);width:100%;font-family:var(--font-sans);color:var(--text-color-primary);gap:calc(var(--spacing)*6);border-radius:var(--radius-density-xl);padding:var(--spacing-density-2xl);flex-direction:column;display:flex}.nv-panel-content{min-height:calc(var(--spacing)*0);flex:1}.nv-panel-root--elevation-low{background-color:var(--background-color-surface-sunken)}.nv-panel-root--elevation-high{background-color:var(--background-color-surface-raised)}.nv-panel-root--elevation-higher{background-color:var(--background-color-surface-overlay)}.nv-panel-header{justify-content:flex-start;align-items:center;gap:calc(var(--spacing)*4);display:flex}.nv-panel-icon{font-size:var(--text-24);color:var(--text-color-base)}.nv-panel-header-heading{width:100%;font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-bold);margin-block:-2px}.nv-panel-footer{justify-content:flex-end;align-items:center;gap:calc(var(--spacing)*2);display:flex}@keyframes nv-popover-in{0%{opacity:0;translate:var(--nv-popover-translate-start)}to{opacity:1;translate:0}}.nv-popover-content{--nv-popover-translate-start:0 -4px;isolation:isolate;gap:calc(var(--spacing)*4);border-radius:var(--radius-md);border:1px solid;border-color:var(--border-color-base);background-color:var(--background-color-surface-overlay);padding:calc(var(--spacing)*4);color:var(--text-color-primary);box-shadow:var(--shadow-md);font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);text-wrap:wrap;z-index:1070;overscroll-behavior:contain;touch-action:pan-y;opacity:0;width:max-content;max-width:min(100vw - 16px,560px);max-height:min(100vh - 16px,560px);translate:var(--nv-popover-translate-start);margin:auto;font-style:normal;position:fixed;inset:auto;overflow:auto}@media (prefers-reduced-motion:no-preference){.nv-popover-content{transition:opacity .25s var(--ease-out),translate .25s var(--ease-out)}}@supports (position-anchor:--a){.nv-popover-content{--nv-popover-offset:4px;position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline;margin:0}.nv-popover-content[data-side=top]{margin-bottom:var(--nv-popover-offset);position-area:top}.nv-popover-content[data-side=top][data-align=start]{position-area:top span-right}.nv-popover-content[data-side=top][data-align=end]{position-area:top span-left}.nv-popover-content[data-side=bottom]{margin-top:var(--nv-popover-offset);position-area:bottom}.nv-popover-content[data-side=bottom][data-align=start]{position-area:bottom span-right}.nv-popover-content[data-side=bottom][data-align=end]{position-area:bottom span-left}.nv-popover-content[data-side=left]{--nv-popover-translate-start:4px 0;margin-right:var(--nv-popover-offset);position-area:left}.nv-popover-content[data-side=left][data-align=start]{position-area:left span-bottom}.nv-popover-content[data-side=left][data-align=end]{position-area:left span-top}.nv-popover-content[data-side=right]{--nv-popover-translate-start:-4px 0;margin-left:var(--nv-popover-offset);position-area:right}.nv-popover-content[data-side=right][data-align=start]{position-area:right span-bottom}.nv-popover-content[data-side=right][data-align=end]{position-area:right span-top}}.nv-popover-trigger,.nv-popover-anchor{anchor-name:var(--nv-anchor-self,--nv-anchor-none),var(--nv-anchor-tooltip,--nv-anchor-none)}:is(.nv-popover-content[data-state=open],.nv-popover-content:popover-open,.nv-popover-content.\:popover-open){opacity:1;display:block;translate:0}@media (prefers-reduced-motion:no-preference){:is(.nv-popover-content[data-state=open],.nv-popover-content:popover-open,.nv-popover-content.\:popover-open){animation:nv-popover-in .25s var(--ease-out)}}.nv-popover-content[data-state=closed]:not(:popover-open):not(.\:popover-open){opacity:0;translate:var(--nv-popover-translate-start)}.nv-progress-bar-root{height:calc(var(--spacing)*2.5);border-radius:var(--radius-xl);background-color:var(--background-color-component-track);width:100%;position:relative;overflow:hidden;transform:translateZ(0);container-type:size}.nv-progress-bar-root--size-small{height:calc(var(--spacing)*1)}.nv-progress-bar-root--size-large{height:calc(var(--spacing)*3.5)}.nv-progress-bar-indicator{inset:calc(var(--spacing)*0);border-radius:inherit;background-color:var(--background-color-interaction-primary-base);height:100%;transition:width .5s cubic-bezier(.65,0,.35,1);position:absolute}.nv-progress-bar-root--indeterminate{--progress-bar-indicator-width:50.0%}.nv-progress-bar-root--indeterminate .nv-progress-bar-indicator{width:var(--progress-bar-indicator-width);animation:1.5s linear infinite progressIndeterminatePosition}@keyframes progressIndeterminatePosition{0%{transform:translate(-100%)}80%,to{transform:translate(100cqw)}}.nv-radio-group-root{gap:calc(var(--spacing)*3);flex-direction:column;width:fit-content;display:flex}.nv-radio-group-root.nv-radio-group-root--orientation-horizontal{flex-direction:row}.nv-radio-group-input:not(.nv-radio-group-input--hidden){height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);border:2px solid;border-color:var(--border-color-interaction-base);background-color:var(--background-color-interaction-base);color:var(--text-color-primary);border-radius:3.40282e38px;flex-shrink:0;place-items:center;display:grid}.nv-radio-group-input:not(.nv-radio-group-input--hidden):focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:2px}input.nv-radio-group-input:not(.nv-radio-group-input--hidden){margin:calc(var(--spacing)*0);cursor:pointer;appearance:none}.nv-radio-group-item{cursor:pointer;align-items:center;gap:calc(var(--spacing)*2);width:fit-content;font-family:var(--font-sans);color:var(--text-color-primary);font-weight:var(--nv-label-font-weight,var(--font-weight-regular));font-size:var(--nv-label-font-size,var(--text-14));line-height:var(--nv-label-line-height,calc(16/14));display:flex}.nv-radio-group-item :where(svg,.nv-icon){color:var(--text-color-base)}@media (prefers-reduced-motion:no-preference){.nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden){transition-duration:.2s;transition-timing-function:var(--ease-out);transition-property:background-color,border-color,border-width}}.nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden):hover{border-color:var(--border-color-interaction-hover);background-color:var(--background-color-interaction-hover)}.nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden):active{border-color:var(--border-color-interaction-pressed);background-color:var(--background-color-interaction-selected)}.nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-indicator{opacity:0;background-color:#0000;scale:2}@media (prefers-reduced-motion:no-preference){.nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-indicator{transition-duration:.25s;transition-timing-function:var(--ease-out);transition-property:background-color,opacity,scale}}.nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):before{content:"";opacity:0;background-color:#0000;border-radius:3.40282e38px;scale:2}@media (prefers-reduced-motion:no-preference){.nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):before{transition-duration:.25s;transition-timing-function:var(--ease-out);transition-property:background-color,opacity,scale}}.nv-radio-group-item:has(input.nv-radio-group-input:checked):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden){border-color:var(--background-color-interaction-primary-base);background-color:var(--text-color-accent-black);border-width:5px;position:relative}.nv-radio-group-item:has(input.nv-radio-group-input:checked):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden):hover{border-color:var(--background-color-interaction-primary-hover)}.nv-radio-group-item:has(input.nv-radio-group-input:checked):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden):active{border-color:var(--background-color-interaction-primary-selected)}.nv-radio-group-item:has(input.nv-radio-group-input:checked):has(.nv-radio-group-input:disabled) .nv-radio-group-input:not(.nv-radio-group-input--hidden){background-color:var(--border-color-interaction-disabled);border-color:#0000}.nv-radio-group-item:has(input.nv-radio-group-input:checked) .nv-radio-group-indicator{width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5);background-color:var(--text-color-accent-black);opacity:1;border-radius:3.40282e38px;scale:1}input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked{border-color:var(--background-color-interaction-primary-base);background-color:var(--text-color-accent-black);border-width:5px;position:relative}input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked:hover{border-color:var(--background-color-interaction-primary-hover)}input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked:active{border-color:var(--background-color-interaction-primary-selected)}input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked:before{content:"";width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5);background-color:var(--text-color-accent-black);opacity:1;border-radius:3.40282e38px;scale:1}input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked:disabled{background-color:var(--border-color-interaction-disabled);border-color:#0000}input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked:disabled:before{background-color:var(--border-color-interaction-disabled)}.nv-radio-group-item:has(.nv-radio-group-input:disabled){cursor:not-allowed;color:var(--text-color-disabled)}input.nv-radio-group-input:not(.nv-radio-group-input--hidden):disabled{cursor:not-allowed;border-color:var(--border-color-disabled);background-color:var(--background-color-interaction-disabled);color:var(--text-color-disabled)}.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden),.nv-radio-group-root--error .nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden){border-color:var(--border-color-feedback-danger);background-color:var(--background-color-interaction-base)}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden),.nv-radio-group-root--error .nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden)):hover{background-color:var(--background-color-feedback-danger-subtle-hover)}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden),.nv-radio-group-root--error .nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden)):active{background-color:var(--background-color-feedback-danger-subtle-pressed)}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):has(input.nv-radio-group-input:checked),.nv-radio-group-root--error .nv-radio-group-item:has(input.nv-radio-group-input:checked)):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden){border-color:var(--border-color-feedback-danger);background-color:var(--text-color-inverse);border-width:5px}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):has(input.nv-radio-group-input:checked),.nv-radio-group-root--error .nv-radio-group-item:has(input.nv-radio-group-input:checked)):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden):hover{border-color:var(--background-color-feedback-danger-hover)}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):has(input.nv-radio-group-input:checked),.nv-radio-group-root--error .nv-radio-group-item:has(input.nv-radio-group-input:checked)):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-input:not(.nv-radio-group-input--hidden):active{border-color:var(--background-color-feedback-danger-pressed)}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):has(input.nv-radio-group-input:checked),.nv-radio-group-root--error .nv-radio-group-item:has(input.nv-radio-group-input:checked)):has(.nv-radio-group-input:disabled) .nv-radio-group-input:not(.nv-radio-group-input--hidden){background-color:var(--border-color-interaction-disabled);color:var(--text-color-disabled);border-color:#0000}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):has(input.nv-radio-group-input:checked),.nv-radio-group-root--error .nv-radio-group-item:has(input.nv-radio-group-input:checked)):not(:has(.nv-radio-group-input:disabled)) .nv-radio-group-indicator{width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5);background-color:var(--text-color-inverse);border-radius:3.40282e38px}.nv-radio-group-item:has(.nv-radio-group-input[data-danger]) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked,.nv-radio-group-root--error input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked{border-color:var(--border-color-feedback-danger);background-color:var(--text-color-inverse);border-width:5px}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked,.nv-radio-group-root--error input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked):hover{border-color:var(--background-color-feedback-danger-hover)}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked,.nv-radio-group-root--error input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked):active{border-color:var(--background-color-feedback-danger-pressed)}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked,.nv-radio-group-root--error input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked):before{content:"";width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5);background-color:var(--text-color-inverse);border-radius:3.40282e38px}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked,.nv-radio-group-root--error input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked):disabled{background-color:var(--border-color-interaction-disabled);color:var(--text-color-disabled);border-color:#0000}:is(.nv-radio-group-item:has(.nv-radio-group-input[data-danger]) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked,.nv-radio-group-root--error input.nv-radio-group-input:not(.nv-radio-group-input--hidden):checked):disabled:before{background-color:var(--border-color-interaction-disabled)}.nv-radio-group-item:has(.nv-radio-group-input[data-danger]):has(.nv-radio-group-input:disabled) .nv-radio-group-input:not(.nv-radio-group-input--hidden),.nv-radio-group-root--error .nv-radio-group-item:has(.nv-radio-group-input:disabled) .nv-radio-group-input:not(.nv-radio-group-input--hidden),.nv-radio-group-item:has(.nv-radio-group-input[data-danger]) input.nv-radio-group-input:not(.nv-radio-group-input--hidden):disabled,.nv-radio-group-root--error input.nv-radio-group-input:not(.nv-radio-group-input--hidden):disabled{border-color:var(--border-color-disabled);background-color:var(--background-color-interaction-disabled);color:var(--text-color-disabled)}.nv-radio-input{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);cursor:pointer;appearance:none;border-color:var(--border-color-interaction-base);background-color:var(--background-color-interaction-base);border-style:solid;border-width:2px;border-radius:3.40282e38px;flex-shrink:0;position:relative}.nv-radio-input:before{content:"";inset:calc(var(--spacing)*0);width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5);background-color:var(--text-color-accent-black);opacity:0;border-radius:3.40282e38px;margin:auto;display:block;position:absolute;scale:0}.nv-radio-input:hover{border-color:var(--border-color-interaction-hover);background-color:var(--background-color-interaction-hover)}.nv-radio-input:active{border-color:var(--border-color-interaction-pressed);background-color:var(--background-color-interaction-selected)}.nv-radio-input:disabled{pointer-events:none;cursor:not-allowed;border-color:var(--border-color-interaction-disabled);background-color:var(--background-color-interaction-disabled)}.nv-radio-input:checked{border-color:var(--background-color-interaction-primary-base);background-color:var(--text-color-accent-black);border-style:solid;border-width:5px}.nv-radio-input:checked:before{opacity:1;scale:1}.nv-radio-input:checked:hover{border-color:var(--background-color-interaction-primary-hover)}.nv-radio-input:checked:active{border-color:var(--background-color-interaction-primary-selected)}.nv-radio-input:checked:disabled{background-color:var(--border-color-interaction-disabled);border-color:#0000}.nv-radio-input:checked:disabled:before{background-color:var(--text-color-disabled)}@media (prefers-reduced-motion:no-preference){.nv-radio-input{transition-property:background-color,border-width,border-color;transition-duration:.2s;transition-timing-function:var(--ease-out)}.nv-radio-input:before{transition-property:opacity,scale;transition-duration:.15s;transition-timing-function:var(--ease-out)}}.nv-radio-group-item:has([data-danger]) .nv-radio-input:not(:disabled),.nv-menu-item--danger .nv-radio-input:not(:disabled),.nv-radio-group-root--error .nv-radio-input:not(:disabled){border-color:var(--border-color-feedback-danger)}:is(.nv-radio-group-item:has([data-danger]) .nv-radio-input:not(:disabled),.nv-menu-item--danger .nv-radio-input:not(:disabled),.nv-radio-group-root--error .nv-radio-input:not(:disabled)):hover{background-color:var(--background-color-feedback-danger-subtle-hover)}:is(.nv-radio-group-item:has([data-danger]) .nv-radio-input:not(:disabled),.nv-menu-item--danger .nv-radio-input:not(:disabled),.nv-radio-group-root--error .nv-radio-input:not(:disabled)):active{background-color:var(--background-color-feedback-danger-subtle-pressed)}:is(.nv-radio-group-item:has([data-danger]) .nv-radio-input:not(:disabled),.nv-menu-item--danger .nv-radio-input:not(:disabled),.nv-radio-group-root--error .nv-radio-input:not(:disabled)):checked{border-color:var(--border-color-feedback-danger);background-color:var(--text-color-inverse)}:is(.nv-radio-group-item:has([data-danger]) .nv-radio-input:not(:disabled),.nv-menu-item--danger .nv-radio-input:not(:disabled),.nv-radio-group-root--error .nv-radio-input:not(:disabled)):checked:hover{border-color:var(--background-color-feedback-danger-hover)}:is(.nv-radio-group-item:has([data-danger]) .nv-radio-input:not(:disabled),.nv-menu-item--danger .nv-radio-input:not(:disabled),.nv-radio-group-root--error .nv-radio-input:not(:disabled)):checked:active{border-color:var(--background-color-feedback-danger-pressed)}.nv-radio-group-item:has(.nv-radio-group-input--hidden){position:relative}input.nv-radio-group-input.nv-radio-group-input--hidden{pointer-events:none;inset:calc(var(--spacing)*0);margin:calc(var(--spacing)*0);appearance:none;border-radius:inherit;opacity:0;width:100%;height:100%;display:block;position:absolute}.nv-radio-group-item:has(input.nv-radio-group-input--hidden:focus-visible){outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:2px}.nv-radio-group-root--kind-tile{gap:calc(var(--spacing)*2);width:100%}.nv-radio-group-root--kind-tile .nv-radio-group-item{width:100%;min-width:calc(var(--spacing)*0);border-radius:var(--radius-md);border:1px solid;border-color:var(--border-color-interaction-base);background-color:var(--background-color-interaction-base);padding:var(--spacing-density-lg);align-items:flex-start}@media (prefers-reduced-motion:no-preference){.nv-radio-group-root--kind-tile .nv-radio-group-item{transition-property:background-color,border-color;transition-duration:.2s;transition-timing-function:var(--ease-out)}}.nv-radio-group-root--kind-tile.nv-radio-group-root--orientation-horizontal{flex-wrap:wrap;align-items:stretch}.nv-radio-group-root--kind-tile.nv-radio-group-root--orientation-horizontal .nv-radio-group-item{flex:1 1 var(--tile-min-width,10rem)}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input):not(:has(.nv-radio-group-input:disabled)):hover{border-color:var(--border-color-interaction-hover);background-color:var(--background-color-interaction-hover)}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input):not(:has(.nv-radio-group-input:disabled)):active{border-color:var(--border-color-interaction-pressed);background-color:var(--background-color-interaction-pressed)}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(input.nv-radio-group-input:focus-visible){outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:2px}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(input.nv-radio-group-input:focus-visible) .nv-radio-group-input{outline:none}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input):has(input.nv-radio-group-input:checked){border-color:var(--border-color-interaction-selected);background-color:var(--background-color-interaction-selected)}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input):has(input.nv-radio-group-input:checked):hover{border-color:var(--border-color-interaction-selected)}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input[data-danger]):not(:has(.nv-radio-group-input:disabled)),.nv-radio-group-root--kind-tile.nv-radio-group-root--error .nv-radio-group-item:not(:has(.nv-radio-group-input:disabled)){border-color:var(--border-color-feedback-danger)}:is(.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input[data-danger]):not(:has(.nv-radio-group-input:disabled)),.nv-radio-group-root--kind-tile.nv-radio-group-root--error .nv-radio-group-item:not(:has(.nv-radio-group-input:disabled))):hover{border-color:var(--border-color-feedback-danger-hover);background-color:var(--background-color-feedback-danger-subtle-hover)}:is(.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input[data-danger]):not(:has(.nv-radio-group-input:disabled)),.nv-radio-group-root--kind-tile.nv-radio-group-root--error .nv-radio-group-item:not(:has(.nv-radio-group-input:disabled))):active{border-color:var(--border-color-feedback-danger);background-color:var(--background-color-feedback-danger-subtle-pressed)}.nv-radio-group-root--kind-tile .nv-radio-group-item:has(.nv-radio-group-input:disabled){border-color:var(--border-color-disabled);background-color:var(--background-color-interaction-disabled)}.nv-segmented-control-root{gap:calc(var(--spacing)*1);border-radius:var(--radius-lg);background-color:var(--background-color-component-track);width:fit-content;height:fit-content;padding:calc(var(--spacing)*1);color:var(--text-color-primary);font-family:var(--font-sans);font-size:var(--text-14);font-weight:var(--font-weight-bold);display:flex;position:relative}.nv-segmented-control-root .nv-segmented-control-item{padding-inline:calc(var(--spacing)*2);padding-block:7px;line-height:1.28571}.nv-segmented-control-root.nv-segmented-control-root--size-tiny .nv-segmented-control-item{padding-inline:calc(var(--spacing)*1);font-size:var(--text-12);padding-block:2px;line-height:1.33333}.nv-segmented-control-root.nv-segmented-control-root--size-small .nv-segmented-control-item{padding-inline:calc(var(--spacing)*1);font-size:var(--text-12);padding-block:4px;line-height:1.33333}.nv-segmented-control-root.nv-segmented-control-root--size-large .nv-segmented-control-item{padding-inline:calc(var(--spacing)*3);font-size:var(--text-16);padding-block:11px;line-height:1.125}.nv-segmented-control-item{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing)*1);border-radius:var(--radius-md);text-align:center;anchor-name:var(--nv-anchor-self,--nv-anchor-none),var(--nv-anchor-tooltip,--nv-anchor-none);flex-grow:1;display:flex;position:relative}.nv-segmented-control-item .nv-segmented-control-input{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (hover:hover){.nv-segmented-control-item:hover{background-color:var(--background-color-interaction-hover)}}.nv-segmented-control-item:has(.nv-segmented-control-input:checked){background-color:var(--background-color-interaction-selected);box-shadow:var(--shadow-sm)}.nv-segmented-control-item:has(.nv-segmented-control-input:focus-visible){outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}@media (prefers-reduced-motion:no-preference){.nv-segmented-control-item{transition-property:color,background-color;transition-duration:.25s;transition-timing-function:var(--ease-out)}}@supports (anchor-name:--nv-segmented-control-selected) and (position-anchor:--nv-segmented-control-selected){.nv-segmented-control-root{isolation:isolate}.nv-segmented-control-root:before{content:"";border-radius:var(--radius-md);background-color:var(--background-color-interaction-selected);position-anchor:--nv-segmented-control-selected;top:anchor(top);left:anchor(left);width:anchor-size(width);height:anchor-size(height);box-shadow:var(--shadow-sm);pointer-events:none;z-index:0;position:absolute}.nv-segmented-control-item{z-index:1}.nv-segmented-control-item:not(:has(.nv-segmented-control-input:checked)):hover{background-color:var(--background-color-interaction-hover)}.nv-segmented-control-item:has(.nv-segmented-control-input:checked){--nv-anchor-self:--nv-segmented-control-selected;box-shadow:none;background-color:#0000}@media (prefers-reduced-motion:no-preference){.nv-segmented-control-root:before{transition-property:left,top,width,height;transition-duration:.25s;transition-timing-function:var(--ease-out)}}}.nv-select-trigger{anchor-name:var(--nv-anchor-self,--nv-anchor-none),var(--nv-anchor-tooltip,--nv-anchor-none)}.nv-select-toggle{flex-shrink:0;align-items:center;height:100%;display:flex}@media (scripting:enabled){.nv-select-native-fallback{display:none}}@media (scripting:none){[data-select-enhanced]{display:none!important}}.nv-select-content[popover]{color:inherit;background:0 0;border:0;margin:0;padding:0;display:none;position:fixed;inset:auto;overflow:visible}.nv-select-content[popover]:popover-open{display:block}.nv-select-content[popover].\:popover-open{display:block}.nv-select-content{--menu-translate-start:0 calc(var(--transition-offset)*-1);transform-origin:top}@supports (position-anchor:--a){.nv-select-content{--nv-select-offset:4px;width:anchor-size(width);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline;margin:0}.nv-select-content[data-side=bottom]{margin-top:var(--nv-select-offset);position-area:bottom span-right}.nv-select-content[data-side=top]{margin-bottom:var(--nv-select-offset);position-area:top span-right}.nv-select-content[data-side=left]{margin-right:var(--nv-select-offset);position-area:left span-bottom}.nv-select-content[data-side=right]{margin-left:var(--nv-select-offset);position-area:right span-bottom}}.nv-select-content[data-side=top]{--menu-translate-start:0 var(--transition-offset);transform-origin:bottom}.nv-select-content[data-side=left]{--menu-translate-start:var(--transition-offset)0;transform-origin:100%}.nv-select-content[data-side=right]{--menu-translate-start:calc(var(--transition-offset)*-1)0;transform-origin:0}@keyframes select-in{0%{translate:var(--menu-translate-start);opacity:0}to{opacity:1;translate:0}}@media (prefers-reduced-motion:no-preference){.nv-select-content:popover-open{animation:select-in .25s var(--ease-out)}}.nv-select-content .nv-menu-root{overscroll-behavior:contain;max-height:min(320px,100vh - 2rem);overflow-y:auto}.nv-select-native-fallback{padding-inline:var(--nv-input-padding)}.nv-select-native-fallback[multiple]{min-height:calc(var(--nv-input-height)*4);padding-block:calc(var(--nv-input-padding)/2)}@media (scripting:none){.nv-select-trigger:has(.nv-select-native-fallback[multiple]){height:auto}.nv-select-trigger:has(.nv-select-native-fallback[multiple]) .nv-input-shell-control:has(.nv-animated-chevron){display:none}}@keyframes left-sidepanel-in{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes left-sidepanel-out{0%{transform:translate(0)}to{transform:translate(-100%)}}@keyframes right-sidepanel-in{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes right-sidepanel-out{0%{transform:translate(0)}to{transform:translate(100%)}}dialog.nv-side-panel-overlay,dialog.nv-side-panel-dialog{margin:calc(var(--spacing)*0);max-width:none;max-height:none;padding:calc(var(--spacing)*0);background-color:#0000;border:none}:is(dialog.nv-side-panel-overlay,dialog.nv-side-panel-dialog):not([open]){display:none}dialog.nv-side-panel-overlay{width:100vw;height:100vh}dialog.nv-side-panel-overlay[open]{inset:calc(var(--spacing)*0);z-index:1000;display:block;position:fixed}dialog.nv-side-panel-overlay::backdrop{background-color:var(--background-color-surface-blanket)}@media (prefers-reduced-motion:no-preference){dialog.nv-side-panel-overlay::backdrop{animation:modal-in .3s var(--ease-out)}dialog.nv-side-panel-overlay[data-state=closed]::backdrop{animation:modal-out .2s var(--ease-out)forwards}}.nv-side-panel-overlay[popover]{margin:calc(var(--spacing)*0);width:100vw;max-width:none;height:100vh;max-height:none;padding:calc(var(--spacing)*0);background-color:#0000;border:none}.nv-side-panel-overlay[popover]:popover-open{inset:calc(var(--spacing)*0);z-index:1000;display:block;position:fixed}.nv-side-panel-overlay[popover]::backdrop{background-color:var(--background-color-surface-blanket)}dialog.nv-side-panel-dialog{pointer-events:none;width:100vw;height:100vh}dialog.nv-side-panel-dialog[open]{inset:calc(var(--spacing)*0);z-index:1000;display:block;position:fixed}dialog.nv-side-panel-dialog::backdrop{background-color:#0000}dialog.nv-side-panel-dialog:has(>.nv-side-panel-content--relative){width:100%;height:100%}dialog.nv-side-panel-dialog:has(>.nv-side-panel-content--relative)[open]{inset:calc(var(--spacing)*0);z-index:1000;position:absolute}.nv-side-panel-content{pointer-events:auto;top:calc(var(--spacing)*0);bottom:calc(var(--spacing)*0);z-index:1030;width:var(--side-panel-width,320px);background-color:var(--background-color-surface-raised);max-width:100%;box-shadow:var(--shadow-lg);font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);color:var(--text-color-primary);--heading-padding-block:calc(var(--spacing-density-xl) + calc(var(--spacing)*3));flex-direction:column;display:flex;position:fixed}.nv-side-panel-content.nv-side-panel-content--relative{top:calc(var(--spacing)*0);bottom:calc(var(--spacing)*0);position:absolute}.nv-side-panel-content.nv-side-panel-content--side-left{left:calc(var(--spacing)*0)}@media (prefers-reduced-motion:no-preference){.nv-side-panel-content.nv-side-panel-content--side-left[data-state=open]{animation:left-sidepanel-in .3s var(--ease-out);animation-fill-mode:both}.nv-side-panel-content.nv-side-panel-content--side-left[data-state=closed]{animation:left-sidepanel-out .2s var(--ease-out)forwards}}.nv-side-panel-content.nv-side-panel-content--side-right{right:calc(var(--spacing)*0)}@media (prefers-reduced-motion:no-preference){.nv-side-panel-content.nv-side-panel-content--side-right[data-state=open]{animation:right-sidepanel-in .3s var(--ease-out);animation-fill-mode:both}.nv-side-panel-content.nv-side-panel-content--side-right[data-state=closed]{animation:right-sidepanel-out .2s var(--ease-out)forwards}}.nv-side-panel-content.nv-side-panel-content--bordered{border:1px solid var(--border-color-base)}.nv-side-panel-content.nv-side-panel-content--bordered .nv-side-panel-heading{border-bottom:1px solid var(--border-color-base)}.nv-side-panel-content.nv-side-panel-content--bordered .nv-side-panel-footer{border-top:1px solid var(--border-color-base)}.nv-side-panel-heading{align-items:center;gap:calc(var(--spacing)*2);width:100%;padding-block:var(--heading-padding-block);padding-right:calc(var(--spacing)*14);padding-left:calc(var(--spacing)*4);font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold);line-height:var(--leading-lh-100);flex-shrink:0;display:flex;position:relative}.nv-side-panel-heading>svg,.nv-side-panel-heading>.nv-icon{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);color:var(--text-color-base);flex-shrink:0}.nv-side-panel-heading.nv-side-panel-heading--hidden{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.nv-side-panel-heading.nv-side-panel-heading--invisible{visibility:hidden}.nv-side-panel-navigation{padding-inline:calc(var(--spacing)*4)}.nv-side-panel-main{min-height:calc(var(--spacing)*0);gap:var(--spacing-density-md);padding:calc(var(--spacing)*4);scrollbar-width:thin;scrollbar-color:var(--nv-scrollbar-color);flex-direction:column;flex:1;margin-block-end:-1px;padding-block-end:calc(var(--spacing)*4 + 1px);display:flex;overflow-y:auto}.nv-side-panel-footer{justify-content:flex-end;align-items:center;gap:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*4);padding-block:var(--spacing-density-xl);flex-shrink:0;display:flex}.nv-side-panel-close{--close-button-top:calc(var(--heading-padding-block) - 1px);top:var(--close-button-top);right:calc(var(--spacing)*4);position:absolute;translate:0 -25%}.nv-side-panel-close>:not(svg):not(.nv-icon){clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.nv-side-panel-portal{pointer-events:none;inset:calc(var(--spacing)*0);z-index:1060;position:fixed}.nv-side-panel-portal>*{pointer-events:auto}.nv-skeleton{background-color:var(--background-color-component-skeleton);width:100%;height:1.3em}@media (prefers-reduced-motion:no-preference){.nv-skeleton.nv-skeleton--animated{animation:var(--animate-pulse)}}.nv-skeleton.nv-skeleton--kind-pill{width:calc(var(--spacing)*16);border-radius:var(--radius-xl)}.nv-skeleton.nv-skeleton--kind-circle{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8);border-radius:3.40282e38px}.nv-slider-root{touch-action:none;-webkit-user-select:none;user-select:none;grid-template-rows:calc(var(--nv-slider-thumb-size)*2);--nv-slider-thumb-size:calc(var(--spacing)*3);--slider-fill-color:var(--color-brand);--slider-track-color:var(--background-color-component-track);grid-template-columns:1fr;align-items:center;width:100%;display:grid;position:relative}.nv-slider-root:where(.nv-slider-root--orientation-vertical){grid-template-rows:1fr;grid-template-columns:calc(var(--nv-slider-thumb-size)*2);width:fit-content;height:100%}.nv-slider-root:has(.nv-slider-input:disabled){--slider-fill-color:var(--text-color-disabled);--slider-track-color:var(--background-color-interaction-disabled)}.nv-slider-root:has(.nv-slider-steps--position-end){row-gap:var(--nv-slider-steps-gap,calc(var(--spacing)*.5));overflow:visible}.nv-slider-root:has(.nv-slider-steps--position-start):not(.nv-slider-root--orientation-vertical){row-gap:var(--nv-slider-steps-gap,calc(var(--spacing)*.5));grid-template-rows:auto calc(var(--nv-slider-thumb-size)*2);overflow:visible}.nv-slider-root--orientation-vertical:has(.nv-slider-steps--position-start){grid-template-columns:auto calc(var(--nv-slider-thumb-size)*2)}.nv-slider-root--orientation-vertical:has(.nv-slider-steps--position-start) .nv-slider-input{left:auto;right:0}.nv-slider-root--orientation-vertical:has(.nv-slider-steps--position-end){grid-template-columns:calc(var(--nv-slider-thumb-size)*2)auto}.nv-slider-root .nv-slider-input{appearance:none;cursor:pointer;width:100%;height:calc(var(--nv-slider-thumb-size)*2);z-index:1;background:0 0;margin:0;position:absolute;top:0;left:0;right:0}.nv-slider-root .nv-slider-input::-webkit-slider-runnable-track{height:calc(var(--spacing)*1);border-radius:var(--radius-xl);background-color:var(--slider-track-color)}.nv-slider-root .nv-slider-input::-moz-range-track{height:calc(var(--spacing)*1);border-radius:var(--radius-xl);background-color:var(--slider-track-color)}.nv-slider-root .nv-slider-input::-webkit-slider-thumb{border:1px solid;border-color:var(--border-color-interaction-strong);background-color:var(--text-color-accent-black);width:var(--nv-slider-thumb-size);height:var(--nv-slider-thumb-size);margin-top:calc((var(--spacing) - var(--nv-slider-thumb-size))/2);border-radius:3.40282e38px}.nv-slider-root .nv-slider-input::-moz-range-thumb{border:1px solid;border-color:var(--border-color-interaction-strong);background-color:var(--text-color-accent-black);width:var(--nv-slider-thumb-size);height:var(--nv-slider-thumb-size);margin-top:calc((var(--spacing) - var(--nv-slider-thumb-size))/2);border-radius:3.40282e38px}.nv-slider-root .nv-slider-input::-webkit-slider-thumb{-webkit-appearance:none;margin-top:calc((var(--spacing) - var(--nv-slider-thumb-size))/2)}.nv-slider-root .nv-slider-input::-moz-range-thumb{box-sizing:border-box}@media (scripting:enabled){.nv-slider-root .nv-slider-input::-webkit-slider-runnable-track{background:linear-gradient(to right,var(--slider-fill-color)var(--slider-percent,0.0%),var(--slider-track-color)var(--slider-percent,0.0%))}.nv-slider-root .nv-slider-input::-moz-range-progress{background-color:var(--slider-fill-color);border-radius:var(--radius-xl);height:calc(var(--spacing)*1)}.nv-slider-root--orientation-vertical .nv-slider-input::-webkit-slider-runnable-track{background:linear-gradient(to top,var(--slider-fill-color)var(--slider-percent,0.0%),var(--slider-track-color)var(--slider-percent,0.0%))}}.nv-slider-root .nv-slider-input:disabled{cursor:default}.nv-slider-root .nv-slider-input:disabled::-webkit-slider-thumb{border-color:var(--border-color-interaction-disabled);background-color:var(--background-color-accent-gray)}.nv-slider-root .nv-slider-input:disabled::-moz-range-thumb{border-color:var(--border-color-interaction-disabled);background-color:var(--background-color-accent-gray)}.nv-slider-root--orientation-vertical .nv-slider-input{writing-mode:vertical-lr;height:100%;width:calc(var(--nv-slider-thumb-size)*2);right:unset;direction:rtl}.nv-slider-root--orientation-vertical .nv-slider-input::-webkit-slider-thumb{margin-left:calc((calc(var(--spacing)*1) - var(--nv-slider-thumb-size))/2)}.nv-slider-root--orientation-vertical .nv-slider-input::-moz-range-thumb{margin-left:calc((calc(var(--spacing)*1) - var(--nv-slider-thumb-size))/2)}.nv-slider-root--orientation-vertical .nv-slider-input::-webkit-slider-runnable-track{border-radius:var(--radius-xl);width:calc(var(--spacing)*1);height:100%}.nv-slider-root--orientation-vertical .nv-slider-input::-moz-range-track{border-radius:var(--radius-xl);background-color:var(--slider-track-color);width:calc(var(--spacing)*1);height:100%}.nv-slider-root--orientation-vertical .nv-slider-input::-moz-range-progress{background-color:var(--slider-fill-color);border-radius:var(--radius-xl);width:calc(var(--spacing)*1)}.nv-slider-steps.nv-slider-steps--position-end{padding-inline:calc(var(--nv-slider-thumb-size)/2);grid-area:2/1;justify-content:space-between;align-self:flex-start;display:flex}.nv-slider-steps.nv-slider-steps--position-start{padding-inline:calc(var(--nv-slider-thumb-size)/2);grid-area:1/1;justify-content:space-between;align-self:flex-end;display:flex}.nv-slider-root--orientation-vertical .nv-slider-steps.nv-slider-steps--position-start{padding-inline:0;padding-block:calc(var(--nv-slider-thumb-size)/2);flex-direction:column-reverse;grid-area:1/1;justify-content:space-between;align-self:stretch;align-items:flex-end;display:flex}.nv-slider-root--orientation-vertical .nv-slider-steps.nv-slider-steps--position-start .nv-slider-step{height:calc(var(--spacing)*0);flex-direction:row-reverse;width:fit-content}.nv-slider-root--orientation-vertical .nv-slider-steps.nv-slider-steps--position-end{padding-inline:0;padding-block:calc(var(--nv-slider-thumb-size)/2);flex-direction:column-reverse;grid-area:1/2;justify-content:space-between;align-self:stretch;align-items:flex-start;display:flex}.nv-slider-root--orientation-vertical .nv-slider-steps.nv-slider-steps--position-end .nv-slider-step{height:calc(var(--spacing)*0);flex-direction:row;width:fit-content}.nv-slider-step{width:calc(var(--spacing)*0);align-items:center;gap:var(--spacing-density-xs);flex-direction:column;display:flex;overflow:visible}.nv-slider-step-dot{height:calc(var(--spacing)*1);width:calc(var(--spacing)*1);background-color:var(--text-color-secondary);border-radius:3.40282e38px;display:block}.nv-slider-step-label{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular);white-space:nowrap;color:var(--text-color-secondary)}.nv-range-slider-root{touch-action:none;-webkit-user-select:none;user-select:none;grid-template-rows:calc(var(--nv-slider-thumb-size)*2);--nv-slider-thumb-size:calc(var(--spacing)*3);width:100%;height:calc(var(--nv-slider-thumb-size)*2);grid-template-columns:1fr;align-items:center;display:grid;position:relative}.nv-range-slider-root:where(.nv-range-slider-root--orientation-vertical){grid-template-rows:1fr;grid-template-columns:calc(var(--nv-slider-thumb-size)*2);justify-items:center;width:fit-content;height:100%}.nv-range-slider-root--orientation-vertical:has(.nv-range-slider-steps--position-start){grid-template-columns:auto calc(var(--nv-slider-thumb-size)*2);column-gap:var(--nv-slider-steps-gap,calc(var(--spacing)*.5))}.nv-range-slider-root--orientation-vertical:has(.nv-range-slider-steps--position-end){grid-template-columns:calc(var(--nv-slider-thumb-size)*2)auto;column-gap:var(--nv-slider-steps-gap,calc(var(--spacing)*.5))}.nv-range-slider-track{height:calc(var(--spacing)*1);border-radius:var(--radius-xl);background-color:var(--background-color-component-track);flex-grow:1;width:100%;position:relative;overflow:hidden}.nv-range-slider-range{border-radius:var(--radius-xl);background-color:var(--color-brand);width:auto;height:100%;position:absolute}.nv-range-slider-range--orientation-vertical{bottom:calc(var(--spacing)*0);background-color:var(--color-brand);width:100%;height:auto}.nv-range-slider-thumb{box-shadow:var(--shadow-sm);cursor:pointer;border:1px solid;border-color:var(--border-color-interaction-strong);background-color:var(--text-color-accent-black);border-radius:3.40282e38px;display:block;position:relative}.nv-range-slider-thumb:focus{outline-style:none}.nv-range-slider-thumb:focus-visible{outline:2px solid var(--nv-focus-outline-color,var(--text-color-primary));outline-offset:2px}.nv-range-slider-thumb:after{content:"";pointer-events:none;inset:calc(var(--spacing)*0);border-radius:inherit;position:absolute}.nv-range-slider-thumb:hover:after{background-color:var(--background-color-interaction-hover)}.nv-range-slider-thumb:active:after{background-color:var(--background-color-interaction-selected)}.nv-range-slider-root .nv-range-slider-thumb{width:var(--nv-slider-thumb-size);height:var(--nv-slider-thumb-size)}.nv-range-slider-root>.nv-range-slider-track,.nv-range-slider-root>:not(.nv-range-slider-track):not(.nv-range-slider-steps):not(.nv-range-slider-native-fallback):not(.nv-range-slider-native-fallback-fields){grid-area:1/1}.nv-range-slider-root--orientation-vertical:has(.nv-range-slider-steps--position-start)>.nv-range-slider-track,.nv-range-slider-root--orientation-vertical:has(.nv-range-slider-steps--position-start)>:not(.nv-range-slider-track):not(.nv-range-slider-steps):not(.nv-range-slider-native-fallback):not(.nv-range-slider-native-fallback-fields){grid-column:2}.nv-range-slider-root--orientation-vertical .nv-range-slider-track{height:100%;width:calc(var(--spacing)*1)}.nv-range-slider-root--orientation-vertical>:not(.nv-range-slider-track):not(.nv-range-slider-steps):not(.nv-range-slider-native-fallback):not(.nv-range-slider-native-fallback-fields){left:calc(var(--nv-slider-thumb-size)/2)}.nv-range-slider-root[data-disabled] .nv-range-slider-track{background-color:var(--background-color-interaction-disabled)}.nv-range-slider-root[data-disabled] .nv-range-slider-range{background-color:var(--text-color-disabled);background-image:none}.nv-range-slider-root[data-disabled] .nv-range-slider-thumb{border-color:var(--border-color-interaction-disabled);background-color:var(--background-color-accent-gray)}.nv-range-slider-native-fallback-fields{gap:var(--spacing-density-sm);min-width:0}.nv-range-slider-native-fallback-field{gap:var(--spacing-density-xs);min-width:0;display:grid}.nv-range-slider-native-fallback{width:100%;min-width:0}.nv-range-slider-native-fallback-label{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular);color:var(--text-color-secondary)}@media (scripting:enabled){.nv-range-slider-native-fallback,.nv-range-slider-native-fallback-fields,.nv-range-slider-native-fallback-field,.nv-range-slider-native-fallback-label,.nv-range-slider-native-fallback-steps{display:none}}@media (scripting:none){.nv-range-slider-root{touch-action:auto;-webkit-user-select:auto;user-select:auto;height:auto;display:block}.nv-range-slider-track,.nv-range-slider-thumb,.nv-range-slider-range,.nv-range-slider-steps,.nv-range-slider-root>:not(.nv-range-slider-track):not(.nv-range-slider-steps):not(.nv-range-slider-native-fallback-fields){display:none!important}.nv-range-slider-native-fallback-fields{display:flex}.nv-range-slider-native-fallback-field{flex:1 1 0}}.nv-range-slider-root:has(.nv-range-slider-steps--position-end):not(.nv-range-slider-root--orientation-vertical){row-gap:var(--nv-slider-steps-gap,calc(var(--spacing)*.5));height:auto}.nv-range-slider-root:has(.nv-range-slider-steps--position-end):not(.nv-range-slider-root--orientation-vertical)>:not(.nv-range-slider-track):not(.nv-range-slider-steps):not(.nv-range-slider-native-fallback):not(.nv-range-slider-native-fallback-fields){top:calc(var(--nv-slider-thumb-size)/2)}.nv-range-slider-root:has(.nv-range-slider-steps--position-start):not(.nv-range-slider-root--orientation-vertical){row-gap:var(--nv-slider-steps-gap,calc(var(--spacing)*.5));grid-template-rows:auto calc(var(--nv-slider-thumb-size)*2);height:auto}.nv-range-slider-root:has(.nv-range-slider-steps--position-start):not(.nv-range-slider-root--orientation-vertical)>:not(.nv-range-slider-track):not(.nv-range-slider-steps):not(.nv-range-slider-native-fallback):not(.nv-range-slider-native-fallback-fields){top:calc(var(--nv-slider-thumb-size)/2)}.nv-range-slider-steps{position:relative}.nv-range-slider-steps.nv-range-slider-steps--position-end{padding-inline:calc(var(--nv-slider-thumb-size)/2);grid-area:2/1;justify-content:space-between;display:flex}.nv-range-slider-steps.nv-range-slider-steps--position-start{padding-inline:calc(var(--nv-slider-thumb-size)/2);grid-area:1/1;justify-content:space-between;display:flex}.nv-range-slider-root--orientation-vertical .nv-range-slider-steps.nv-range-slider-steps--position-start{padding-inline:0;padding-block:calc(var(--nv-slider-thumb-size)/2);flex-direction:column-reverse;grid-area:1/1;justify-content:space-between;align-self:stretch;align-items:flex-end;display:flex}.nv-range-slider-root--orientation-vertical .nv-range-slider-steps.nv-range-slider-steps--position-start .nv-range-slider-step{height:calc(var(--spacing)*0);flex-direction:row-reverse;width:fit-content}.nv-range-slider-root--orientation-vertical .nv-range-slider-steps.nv-range-slider-steps--position-end{padding-inline:0;padding-block:calc(var(--nv-slider-thumb-size)/2);flex-direction:column-reverse;grid-area:1/2;justify-content:space-between;align-self:stretch;align-items:flex-start;display:flex}.nv-range-slider-root--orientation-vertical .nv-range-slider-steps.nv-range-slider-steps--position-end .nv-range-slider-step{height:calc(var(--spacing)*0);flex-direction:row;width:fit-content}.nv-range-slider-step{width:calc(var(--spacing)*0);align-items:center;gap:var(--spacing-density-xs);flex-direction:column;display:flex;overflow:visible}.nv-range-slider-step-dot{height:calc(var(--spacing)*1);width:calc(var(--spacing)*1);background-color:var(--text-color-secondary);border-radius:3.40282e38px;display:block}.nv-range-slider-step-label{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular);white-space:nowrap;color:var(--text-color-secondary)}.nv-spinner-root{filter:drop-shadow(0 0 calc(var(--spinner-shadow-size)*2)#76b90080);flex-direction:column;align-items:center;display:flex}@supports (color:color-mix(in lab, red, red)){.nv-spinner-root{filter:drop-shadow(0 0 calc(var(--spinner-shadow-size)*2)color-mix(in srgb,var(--background-color-interaction-primary-base)50.0%,transparent))}}.nv-spinner-root svg{width:auto}.nv-spinner-root--size-small{gap:calc(var(--spacing)*2);--spinner-shadow-size:calc(var(--spacing)*1.5)}.nv-spinner-root--size-small>div:first-child{height:calc(var(--spacing)*8)}.nv-spinner-root--size-medium{gap:calc(var(--spacing)*3);--spinner-shadow-size:calc(var(--spacing)*2)}.nv-spinner-root--size-medium>div:first-child{height:calc(var(--spacing)*16)}.nv-spinner-root--size-large{gap:calc(var(--spacing)*6);--spinner-shadow-size:calc(var(--spacing)*3)}.nv-spinner-root--size-large>div:first-child{height:calc(var(--spacing)*32)}.nv-spinner-arrow{fill:var(--background-color-interaction-primary-base);width:0;height:0;animation:1s infinite spinnerArrowOpacity}.nv-spinner-description{font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);color:var(--text-color-primary)}@keyframes spinnerArrowOpacity{0%{opacity:.1}30%{opacity:1}to{opacity:.1}}.nv-stepper-root{gap:calc(var(--spacing)*3);display:flex}.nv-stepper-root:not(.nv-stepper-root--layout-vertical){width:100%;overflow:auto}.nv-stepper-root.nv-stepper-root--layout-vertical{flex-direction:column;height:100%}.nv-stepper-root.nv-stepper-root--kind-compact{gap:calc(var(--spacing)*1)}.nv-stepper-root.nv-stepper-root--kind-compact.nv-stepper-root--layout-horizontal{flex-direction:column}.nv-stepper-root.nv-stepper-root--kind-compact.nv-stepper-root--layout-vertical{flex-direction:column;align-items:flex-start;height:auto}.nv-stepper-item{flex-direction:column;display:flex}.nv-stepper-root--layout-horizontal>.nv-stepper-item{flex:1}.nv-stepper-root--kind-compact .nv-stepper-item{min-width:calc(var(--spacing)*0);flex:none}.nv-stepper-node-row{align-items:center;gap:calc(var(--spacing)*3);flex-shrink:0;width:100%;display:flex}.nv-stepper-node{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);border:2px solid;border-color:var(--border-color-base);border-radius:3.40282e38px;flex-shrink:0;justify-content:center;align-items:center;display:flex;position:relative;overflow:clip}.nv-stepper-item[data-state=completed] .nv-stepper-node{background-color:var(--background-color-interaction-primary-base);border-color:#0000}.nv-stepper-item[data-state=active] .nv-stepper-node{border-color:var(--border-color-interaction-selected)}.nv-stepper-item[data-status=error] .nv-stepper-node{border-color:var(--border-color-feedback-danger);background-color:#0000}.nv-stepper-root--kind-compact .nv-stepper-node{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.nv-stepper-node:has(a,button){cursor:pointer;position:relative}.nv-stepper-node:has(a,button) a:after,.nv-stepper-node:has(a,button) button:after{content:"";position:absolute;inset:0}.nv-stepper-node-icon{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3);justify-content:center;align-items:center;display:flex}.nv-stepper-node-icon svg,.nv-stepper-node-icon .nv-icon{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3);color:var(--text-color-accent-black)}.nv-stepper-item[data-status=error] .nv-stepper-node-icon.nv-stepper-node-icon--error svg,.nv-stepper-item[data-status=error] .nv-stepper-node-icon.nv-stepper-node-icon--error .nv-icon{color:var(--text-color-feedback-danger)}.nv-stepper-node-number{font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-regular);color:var(--text-color-secondary)}.nv-stepper-item[data-state=active] .nv-stepper-node-number{color:var(--text-color-strong)}.nv-stepper-item[data-state=completed] .nv-stepper-node-number{inset:calc(var(--spacing)*0);color:#0000;position:absolute}.nv-stepper-item-heading{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold);white-space:nowrap;color:var(--text-color-secondary)}.nv-stepper-item[data-state=active] .nv-stepper-item-heading,.nv-stepper-item[data-state=completed] .nv-stepper-item-heading{color:var(--text-color-primary)}.nv-stepper-item-body{gap:calc(var(--spacing)*3);flex-direction:column;display:flex}.nv-stepper-root--layout-horizontal:not(.nv-stepper-root--kind-compact)>.nv-stepper-item>.nv-stepper-item-body{padding-left:calc(var(--spacing)*9)}.nv-stepper-item-description{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);color:var(--text-color-secondary)}.nv-stepper-root--kind-compact .nv-stepper-item-description{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular)}.nv-stepper-item[data-state=default] .nv-stepper-item-description{color:var(--text-color-placeholder)}.nv-stepper-item-content{display:flex}.nv-stepper-root--layout-horizontal:not(.nv-stepper-root--kind-compact)>.nv-stepper-item:not(:last-child)>.nv-stepper-node-row:after{content:"";height:calc(var(--spacing)*.5);background-image:repeating-linear-gradient(to right,var(--border-color-base)0,var(--border-color-base)var(--spacing),transparent var(--spacing),transparent calc(var(--spacing)*2));border-radius:3.40282e38px;flex:1;display:block}.nv-stepper-root--layout-horizontal:not(.nv-stepper-root--kind-compact)>.nv-stepper-item[data-state=completed]:not(:last-child)>.nv-stepper-node-row:after{background:var(--border-color-interaction-selected)}.nv-stepper-root--layout-vertical:not(.nv-stepper-root--kind-compact)>.nv-stepper-item:not(:last-child)>.nv-stepper-node-row:after{content:"";min-height:calc(var(--spacing)*6);width:calc(var(--spacing)*.5);background-image:repeating-linear-gradient(to bottom,var(--border-color-base)0,var(--border-color-base)var(--spacing),transparent var(--spacing),transparent calc(var(--spacing)*2));border-radius:3.40282e38px;flex:1;display:block}.nv-stepper-root--layout-vertical:not(.nv-stepper-root--kind-compact)>.nv-stepper-item[data-state=completed]:not(:last-child)>.nv-stepper-node-row:after{background:var(--border-color-interaction-selected)}.nv-stepper-compact-dots{align-items:center;display:flex}.nv-stepper-root--layout-vertical>.nv-stepper-compact-dots{flex-direction:column}.nv-stepper-compact-dots>.nv-stepper-item{flex-direction:row;align-items:center;display:flex}.nv-stepper-compact-dots>.nv-stepper-item>.nv-stepper-node-row{width:auto}.nv-stepper-compact-dots>.nv-stepper-item:not(:last-child):after{content:"";height:calc(var(--spacing)*.5);width:calc(var(--spacing)*4);background-image:repeating-linear-gradient(to right,var(--border-color-base)0,var(--border-color-base)var(--spacing),transparent var(--spacing),transparent calc(var(--spacing)*2));border-radius:3.40282e38px;margin-inline:4px;display:block}.nv-stepper-compact-dots>.nv-stepper-item[data-state=completed]:not(:last-child):after{background:var(--border-color-interaction-selected)}.nv-stepper-root--layout-vertical .nv-stepper-compact-dots>.nv-stepper-item{flex-direction:column;align-items:center;display:flex}.nv-stepper-root--layout-vertical .nv-stepper-compact-dots>.nv-stepper-item:not(:last-child):after{width:calc(var(--spacing)*.5);background-image:repeating-linear-gradient(to bottom,var(--border-color-base)0,var(--border-color-base)var(--spacing),transparent var(--spacing),transparent calc(var(--spacing)*2));height:16px;margin-block:4px}.nv-stepper-active-info{flex-direction:column;justify-content:center;align-items:flex-start;gap:1px;display:flex}.nv-stepper-active-info>.nv-stepper-item-heading{color:var(--text-color-primary)}.nv-stepper-item-text{min-height:calc(var(--spacing)*6);flex-direction:column;justify-content:center;align-items:flex-start;gap:1px;display:flex}.nv-stepper-root--layout-vertical:not(.nv-stepper-root--kind-compact)>.nv-stepper-item{gap:calc(var(--spacing)*3);flex-direction:row;flex:1;width:100%;min-height:60px}.nv-stepper-root--layout-vertical:not(.nv-stepper-root--kind-compact)>.nv-stepper-item>.nv-stepper-node-row{width:calc(var(--spacing)*6);align-items:center;gap:calc(var(--spacing)*3);flex-direction:column;align-self:stretch}.nv-stepper-root--layout-vertical:not(.nv-stepper-root--kind-compact)>.nv-stepper-item>.nv-stepper-item-body{gap:calc(var(--spacing)*3);padding-top:calc(var(--spacing)*0);padding-left:calc(var(--spacing)*0);flex:1}.nv-stepper-root--layout-vertical:not(.nv-stepper-root--kind-compact)>.nv-stepper-item>.nv-stepper-item-body>.nv-stepper-item-heading{white-space:normal}.nv-status-indicator{width:var(--size);height:var(--size);background-color:var(--color);border-radius:3.40282e38px;display:inline-block}.nv-status-indicator,.nv-status-indicator.nv-status-indicator--size-medium{--size:8px}.nv-status-indicator.nv-status-indicator--size-small{--size:6px}.nv-status-indicator.nv-status-indicator--size-large{--size:12px}.nv-status-indicator.nv-status-indicator--size-xlarge{--size:16px}.nv-status-indicator.nv-status-indicator--size-xxlarge{--size:20px}.nv-status-indicator,.nv-status-indicator.nv-status-indicator--color-red{--color:var(--text-color-feedback-danger)}.nv-status-indicator.nv-status-indicator--color-blue{--color:var(--text-color-feedback-info)}.nv-status-indicator.nv-status-indicator--color-yellow{--color:var(--text-color-feedback-warning)}.nv-status-indicator.nv-status-indicator--color-green{--color:var(--text-color-feedback-success)}.nv-status-message-root{justify-content:center;align-items:center;gap:calc(var(--spacing)*4);width:fit-content;min-width:200px;font-family:var(--font-sans);color:var(--text-color-primary);flex-direction:column;margin-inline:auto;display:flex}.nv-status-message-root .nv-status-message-heading{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-status-message-root .nv-status-message-actions{justify-content:center;align-items:center;gap:calc(var(--spacing)*2);width:100%;display:flex}.nv-status-message-root .nv-status-message-header{justify-content:center;align-items:center;gap:calc(var(--spacing)*2);text-align:center;flex-direction:column;width:100%;display:flex}.nv-status-message-root .nv-status-message-footer{justify-content:center;align-items:center;gap:calc(var(--spacing)*2);width:100%;display:flex}.nv-status-message-root .nv-status-message-subheading{font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);color:var(--text-color-secondary)}.nv-status-message-root .nv-status-message-media{font-size:var(--text-64);color:var(--text-color-base);justify-content:center;align-items:center;display:flex}.nv-status-message-root.nv-status-message-root--size-small .nv-status-message-heading{font-family:var(--font-sans);font-size:var(--text-18);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-status-message-root.nv-status-message-root--size-small .nv-status-message-media{font-size:var(--text-32)}.nv-status-message-root.nv-status-message-root--size-small .nv-status-message-actions{gap:calc(var(--spacing)*1.5)}.nv-status-message-root.nv-status-message-root--size-small .nv-status-message-header{gap:calc(var(--spacing)*1)}.nv-switch-root{align-items:center;gap:calc(var(--spacing)*2);--nv-switch-track-height:calc(var(--spacing)*6);--nv-switch-track-width:calc(var(--spacing)*11);--nv-switch-thumb-size:calc(var(--spacing)*4);--nv-switch-thumb-margin:calc(var(--spacing)*1);--nv-switch-thumb-margin-checked:calc(var(--spacing)*6);width:fit-content;display:inline-flex}.nv-switch-root.nv-switch-root--size-small{gap:calc(var(--spacing)*1);--nv-switch-track-height:calc(var(--spacing)*4);--nv-switch-track-width:calc(var(--spacing)*7);--nv-switch-thumb-size:calc(var(--spacing)*2);--nv-switch-thumb-margin-checked:calc(var(--spacing)*4)}.nv-switch-root.nv-switch-root--size-large{--nv-switch-track-height:calc(var(--spacing)*8);--nv-switch-track-width:calc(var(--spacing)*15);--nv-switch-thumb-size:calc(var(--spacing)*6);--nv-switch-thumb-margin-checked:calc(var(--spacing)*8)}.nv-switch-root.nv-switch-root--side-start{flex-direction:row-reverse}.nv-switch-input{appearance:none;cursor:pointer;border-radius:var(--radius-xl);height:var(--nv-switch-track-height);width:var(--nv-switch-track-width);box-shadow:inset 0 0 0 2px var(--border-color-interaction-strong);background-color:#0000;position:relative;overflow:hidden;scale:1}.nv-switch-input:focus-visible{outline:2px solid var(--nv-focus-outline-color,var(--text-color-primary));outline-offset:2px}.nv-switch-input:before{content:"";box-shadow:var(--shadow-sm);z-index:1;border-radius:var(--radius-xl);background-color:var(--background-color-interaction-inverse);top:50%;left:var(--nv-switch-thumb-margin);width:var(--nv-switch-thumb-size);height:var(--nv-switch-thumb-size);transition:left .3s var(--ease-out),background-color .3s var(--ease-out);display:block;position:absolute;transform:translateY(-50%)}@media (prefers-reduced-motion:reduce){.nv-switch-input:before{transition:none}}.nv-switch-input:after{content:"";inset:calc(var(--spacing)*0);z-index:calc(1*-1);border-radius:var(--radius-xl);background-color:var(--background-color-interaction-primary-base);transition:transform .25s var(--ease-out);position:absolute;transform:translate(-100%)}@media (prefers-reduced-motion:reduce){.nv-switch-input:after{transition:none}}.nv-switch-input:is(:checked,[data-state=checked]):before{left:var(--nv-switch-thumb-margin-checked);background-color:var(--text-color-accent-black)}.nv-switch-input:is(:checked,[data-state=checked]):after{transform:translate(0)}.nv-switch-input:is(:disabled,[data-disabled]){cursor:not-allowed;box-shadow:inset 0 0 0 2px var(--border-color-disabled)}.nv-switch-input:is(:disabled,[data-disabled]):before{background-color:var(--text-color-disabled)}.nv-switch-input:is(:disabled,[data-disabled]):is(:checked,[data-state=checked]){box-shadow:none}.nv-switch-input:is(:disabled,[data-disabled]):is(:checked,[data-state=checked]):after{background-color:var(--background-color-interaction-disabled-checked)}.nv-switch-input:is(:disabled,[data-disabled]):is(:checked,[data-state=checked]):before{background-color:var(--text-color-interaction-disabled-checked)}.nv-table-root{--table-cell-inline-padding:var(--spacing-density-lg);--table-cell-block-padding:var(--spacing-density-md);--table-cell-content-height:40px;background-color:var(--background-color-surface-base);font-size:var(--text-14);line-height:var(--leading-lh-150);color:var(--text-color-primary)}.nv-table-root,.nv-table-root.nv-table-root--layout-fixed{table-layout:fixed}.nv-table-root.nv-table-root--layout-auto{table-layout:auto}@media (hover:hover){:where(.nv-table-root.nv-table-root--hoverable-rows .nv-table-body .nv-table-row):hover{background-color:var(--background-color-interaction-hover)}}.nv-table--align-left{text-align:left}.nv-table--align-center{text-align:center}.nv-table--align-right{text-align:right}.nv-table-row{border-bottom:1px solid;border-color:var(--border-color-base)}.nv-table-row.nv-table-row--selected{background-color:var(--background-color-interaction-pressed)}.nv-table-data-cell,.nv-table-header-cell{text-overflow:ellipsis;white-space:nowrap;padding-inline:var(--table-cell-inline-padding);padding-block:var(--table-cell-block-padding);vertical-align:middle;box-sizing:border-box;height:calc(var(--table-cell-content-height) + var(--table-cell-block-padding)*2);align-items:center;overflow:hidden}:is(.nv-table-data-cell,.nv-table-header-cell) [data-sorting-icon]{color:var(--text-color-base)}:is(.nv-table-data-cell,.nv-table-header-cell) [data-sorting-icon][data-selected]{color:var(--text-color-primary)}.nv-table-head{border-bottom:2px solid;border-color:var(--border-color-base);font-weight:var(--font-weight-semibold)}.nv-table-body{font-weight:var(--font-weight-regular)}.nv-table-toolbar{height:calc(var(--spacing)*12);position:relative}.nv-table-toolbar [data-active=false]{opacity:0;translate:0 130%}.nv-table-toolbar [data-active=true]{opacity:1;translate:0}.nv-table-toolbar .nv-table-toolbar-content{justify-content:space-between;align-items:center;gap:calc(var(--spacing)*1);width:100%;height:100%;padding-block:var(--spacing-density-xs);display:flex}.nv-table-toolbar .nv-table-toolbar-bulk-actions-section{inset:calc(var(--spacing)*0);position:absolute}@media (prefers-reduced-motion:no-preference){:is(.nv-table-toolbar .nv-table-toolbar-content,.nv-table-toolbar .nv-table-toolbar-bulk-actions-section){transition-property:translate,opacity;transition-duration:.15s;transition-timing-function:var(--ease-out)}}.nv-table-bulk-action-toolbar{justify-content:space-between;align-items:center;gap:calc(var(--spacing)*1);background-color:var(--background-color-component-track);padding-inline:var(--spacing-density-xl);padding-block:var(--spacing-density-xs);display:flex}.nv-tabs-root{gap:inherit;flex-direction:column;width:100%;max-width:100%;display:flex;overflow:hidden}.nv-tabs-content{align-items:flex-start;gap:calc(var(--spacing)*4);padding:calc(var(--spacing)*6);flex-direction:column;align-self:stretch;display:flex}.nv-tabs-content.nv-tabs-content--padding-none{padding:calc(var(--spacing)*0)}.nv-tabs-content:not([data-active]){display:none}.nv-tabs-list{align-items:center;gap:calc(var(--spacing)*2);flex-wrap:nowrap;width:100%;display:flex;position:relative}.nv-tabs-list .nv-tabs-trigger{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing)*2);min-width:fit-content;font-size:var(--text-14);line-height:var(--leading-lh-100);color:var(--text-color-secondary);font-weight:var(--font-weight-regular);flex-shrink:0;display:inline-flex}.nv-tabs-list .nv-tabs-trigger:hover,.nv-tabs-list .nv-tabs-trigger:active,.nv-tabs-list .nv-tabs-trigger:focus-visible{color:var(--text-color-primary)}.nv-tabs-list .nv-tabs-trigger:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}.nv-tabs-list .nv-tabs-trigger .nv-tabs-trigger-visible,.nv-tabs-list .nv-tabs-trigger .nv-tabs-trigger-invisible{justify-content:center;align-items:center;gap:inherit;display:inline-flex}.nv-tabs-list .nv-tabs-trigger .nv-tabs-trigger-visible{position:absolute}.nv-tabs-list .nv-tabs-trigger .nv-tabs-trigger-invisible{visibility:hidden;font-weight:var(--font-weight-bold)}@media (prefers-reduced-motion:no-preference){.nv-tabs-list .nv-tabs-trigger{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.2s;transition-timing-function:var(--ease-out)}}.nv-tabs-list .nv-tabs-trigger[data-active]{color:var(--text-color-primary);font-weight:var(--font-weight-bold)}.nv-tabs-list .nv-tabs-trigger[data-active]>svg,.nv-tabs-list .nv-tabs-trigger[data-active]>.nv-icon{color:var(--text-color-brand)}.nv-tabs-list .nv-tabs-trigger:disabled,.nv-tabs-list .nv-tabs-trigger[data-disabled]{cursor:not-allowed;color:var(--text-color-disabled);background-color:#0000;border-color:#0000}.nv-tabs-list .nv-tabs-scroll-button{height:calc(var(--spacing)*10);width:calc(var(--spacing)*10)}.nv-tabs-list.nv-tabs-list--kind-primary{box-shadow:inset 0 -2px 0 0 var(--border-color-base)}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-scroll-container{white-space:nowrap}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-scroll-shadow{height:40px}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger{height:calc(var(--spacing)*10);border-radius:var(--radius-none);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1);border-bottom:2px solid #0000;position:relative;overflow-x:clip}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:disabled{cursor:not-allowed}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:before,.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:after{content:"";pointer-events:none;z-index:50;margin-top:calc(var(--spacing)*1);border-bottom:4px solid #0000;width:100%;height:100%;position:absolute;left:-100%}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:after{left:100%}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:not(:disabled):where(:hover,:active,:focus-visible){border-bottom-color:var(--border-color-interaction-hover)}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:where(.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger[data-active]){border-bottom-color:var(--border-color-interaction-hover)}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:where(.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger[data-active]):before,.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:where(.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger[data-active]):after{left:calc(var(--spacing)*0);border-bottom-color:var(--border-color-interaction-selected);border-bottom-width:4px}@media (prefers-reduced-motion:no-preference){.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:before{transition:left 0s var(--ease-out),border-color 0s var(--ease-out)}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:after{transition:left .2s var(--ease-out),border-color 0s var(--ease-out).2s}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:where(.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger[data-active]):before{transition:left .2s var(--ease-out),border-color 0s var(--ease-out)}.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger:where(.nv-tabs-list.nv-tabs-list--kind-primary .nv-tabs-trigger[data-active]):after{transition:left 0s var(--ease-out).2s,border-color 0s var(--ease-out).2s}}.nv-tabs-list.nv-tabs-list--kind-secondary .nv-tabs-scroll-container{height:calc(var(--spacing)*8);gap:calc(var(--spacing)*2)}.nv-tabs-list.nv-tabs-list--kind-secondary .nv-tabs-scroll-shadow{height:calc(var(--spacing)*8)}.nv-tabs-list.nv-tabs-list--kind-secondary .nv-tabs-trigger{border-radius:var(--radius-3xl);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1)}.nv-tabs-list.nv-tabs-list--kind-secondary .nv-tabs-trigger:not(:disabled):where(:hover,:focus-visible){background-color:var(--background-color-interaction-hover)}.nv-tabs-list.nv-tabs-list--kind-secondary .nv-tabs-trigger:active{background-color:var(--background-color-interaction-pressed)}.nv-tabs-list.nv-tabs-list--kind-secondary .nv-tabs-trigger[data-active]{background-color:var(--background-color-interaction-hover)}.nv-tabs-list.nv-tabs-list--kind-tertiary .nv-tabs-scroll-container{gap:calc(var(--spacing)*2);height:22px}.nv-tabs-list.nv-tabs-list--kind-tertiary .nv-tabs-scroll-shadow{height:22px}.nv-tabs-list.nv-tabs-list--kind-tertiary .nv-tabs-scroll-shadow:first-child{left:calc(var(--spacing)*8)}.nv-tabs-list.nv-tabs-list--kind-tertiary .nv-tabs-scroll-shadow:last-child{right:calc(var(--spacing)*8)}.nv-tabs-list.nv-tabs-list--kind-tertiary .nv-tabs-trigger{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1)}.nv-tabs-list.nv-tabs-list--kind-tertiary .nv-tabs-trigger:has(>svg:only-child,>.nv-icon:only-child){padding:calc(var(--spacing)*1)}.nv-tabs-scroll-container{scrollbar-width:none;-ms-overflow-style:none;flex-wrap:nowrap;align-items:center;width:100%;display:flex;position:relative;overflow-x:auto}.nv-tabs-scroll-container::-webkit-scrollbar{display:none}.nv-tabs-scroll-container.nv-tabs-scroll-container--fade-left{-webkit-mask-image:linear-gradient(90deg,#0000 0%,#000 5%);mask-image:linear-gradient(90deg,#0000 0%,#000 5%)}.nv-tabs-scroll-container.nv-tabs-scroll-container--fade-right{-webkit-mask-image:linear-gradient(270deg,#0000 0%,#000 5%);mask-image:linear-gradient(270deg,#0000 0%,#000 5%)}.nv-tabs-scroll-container.nv-tabs-scroll-container--fade-both{-webkit-mask-image:linear-gradient(90deg,#0000 0%,#000 5% 95%,#0000 100%);mask-image:linear-gradient(90deg,#0000 0%,#000 5% 95%,#0000 100%)}.nv-tabs-scroll-container-ellipses{padding-inline:calc(var(--spacing)*3);font-size:var(--text-14);color:var(--text-color-primary);font-weight:var(--font-weight-semibold)}.nv-tag{border-radius:var(--radius-3xl);align-items:center;gap:calc(var(--spacing)*1);width:fit-content;max-width:100%;height:fit-content;padding-inline:var(--spacing-density-lg);padding-block:calc(var(--spacing-density-sm) - 2px);font-family:var(--font-sans);font-size:var(--text-12);font-weight:var(--font-weight-semibold);vertical-align:middle;--bg-color:var(--background-color-accent-blue-subtle);--border-color:var(--border-color-accent-blue);--text-color:var(--text-color-accent-blue);--hover-bg-color:var(--background-color-accent-blue-subtle-hover);--hover-text-color:var(--text-color-accent-blue);--active-bg-color:var(--background-color-accent-blue-subtle-selected);--active-text-color:var(--text-color-accent-white);border:1px solid;border-color:var(--border-color);background-color:var(--bg-color);color:var(--text-color);flex-grow:0;flex-shrink:0;line-height:1.33333;display:inline-flex}.nv-tag svg,.nv-tag .nv-icon{flex-shrink:0;width:1em;height:1em}.nv-tag:disabled{cursor:not-allowed;border-color:var(--border-color-disabled);background-color:var(--background-color-interaction-disabled);color:var(--text-color-disabled)}.nv-tag:where(:not([disabled],[data-readonly])){cursor:pointer}@media (hover:hover){.nv-tag:where(:not([disabled],[data-readonly])):hover{color:var(--hover-text-color)}}@media (hover:hover){.nv-tag:where(:not([disabled],[data-readonly])):hover{background:var(--hover-bg-color)}}.nv-tag:where(:not([disabled],[data-readonly])):active{background-color:var(--active-bg-color);color:var(--active-text-color)}@media (prefers-reduced-motion:no-preference){.nv-tag:where(:not([disabled],[data-readonly])){transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.2s;transition-timing-function:var(--ease-out)}}.nv-tag:where(:not([disabled],[data-readonly])):focus-visible,.nv-tag:where(:not([disabled],[data-readonly])):has(:focus-visible){outline:2px solid var(--nv-focus-outline-color,var(--text-color-primary));outline-offset:-2px}.nv-tag:where([data-readonly]){cursor:default}.nv-tag:where(.nv-tag--kind-outline){--bg-color:transparent;--border-color:var(--border-color-accent-blue);--text-color:var(--text-color-accent-blue);--hover-bg-color:var(--background-color-accent-blue-hover);--hover-text-color:var(--text-color-accent-blue);--active-bg-color:var(--background-color-accent-blue-selected);--active-text-color:var(--text-color-accent-blue)}.nv-tag:where(.nv-tag--kind-outline):disabled{background-color:#0000}.nv-tag:where(.nv-tag--color-green){--bg-color:var(--background-color-accent-green-subtle);--border-color:var(--border-color-accent-green);--text-color:var(--text-color-accent-green);--hover-bg-color:var(--background-color-accent-green-subtle-hover);--hover-text-color:var(--text-color-accent-green);--active-bg-color:var(--background-color-accent-green-subtle-selected)}.nv-tag:where(.nv-tag--color-green):where(.nv-tag--kind-outline){--bg-color:inherit;--border-color:var(--border-color-accent-green);--text-color:var(--text-color-accent-green);--hover-bg-color:var(--background-color-accent-green-hover);--hover-text-color:var(--text-color-accent-green);--active-bg-color:var(--background-color-accent-green-selected);--active-text-color:var(--text-color-accent-green)}.nv-tag:where(.nv-tag--color-yellow){--bg-color:var(--background-color-accent-yellow-subtle);--border-color:var(--border-color-accent-yellow);--text-color:var(--text-color-accent-yellow);--hover-bg-color:var(--background-color-accent-yellow-subtle-hover);--hover-text-color:var(--text-color-accent-yellow);--active-bg-color:var(--background-color-accent-yellow-subtle-selected)}.nv-tag:where(.nv-tag--color-yellow):where(.nv-tag--kind-outline){--bg-color:inherit;--border-color:var(--border-color-accent-yellow);--text-color:var(--text-color-accent-yellow);--hover-bg-color:var(--background-color-accent-yellow-hover);--hover-text-color:var(--text-color-accent-yellow);--active-bg-color:var(--background-color-accent-yellow-selected);--active-text-color:var(--text-color-accent-yellow)}.nv-tag:where(.nv-tag--color-purple){--bg-color:var(--background-color-accent-purple-subtle);--border-color:var(--border-color-accent-purple);--text-color:var(--text-color-accent-purple);--hover-bg-color:var(--background-color-accent-purple-subtle-hover);--hover-text-color:var(--text-color-accent-purple);--active-bg-color:var(--background-color-accent-purple-subtle-selected)}.nv-tag:where(.nv-tag--color-purple):where(.nv-tag--kind-outline){--bg-color:inherit;--border-color:var(--border-color-accent-purple);--text-color:var(--text-color-accent-purple);--hover-bg-color:var(--background-color-accent-purple-hover);--hover-text-color:var(--text-color-accent-purple);--active-bg-color:var(--background-color-accent-purple-selected);--active-text-color:var(--text-color-accent-purple)}.nv-tag:where(.nv-tag--color-red){--bg-color:var(--background-color-accent-red-subtle);--border-color:var(--border-color-accent-red);--text-color:var(--text-color-accent-red);--hover-bg-color:var(--background-color-accent-red-subtle-hover);--hover-text-color:var(--text-color-accent-red);--active-bg-color:var(--background-color-accent-red-subtle-selected)}.nv-tag:where(.nv-tag--color-red):where(.nv-tag--kind-outline){--bg-color:inherit;--text-color:var(--text-color-accent-red);--border-color:var(--border-color-accent-red);--hover-bg-color:var(--background-color-accent-red-hover);--hover-text-color:var(--text-color-accent-red);--active-bg-color:var(--background-color-accent-red-selected);--active-text-color:var(--text-color-accent-red)}.nv-tag:where(.nv-tag--color-teal){--bg-color:var(--background-color-accent-teal-subtle);--border-color:var(--border-color-accent-teal);--text-color:var(--text-color-accent-teal);--hover-bg-color:var(--background-color-accent-teal-subtle-hover);--hover-text-color:var(--text-color-accent-teal);--active-bg-color:var(--background-color-accent-teal-subtle-selected)}.nv-tag:where(.nv-tag--color-teal):where(.nv-tag--kind-outline){--bg-color:inherit;--border-color:var(--border-color-accent-teal);--text-color:var(--text-color-accent-teal);--hover-bg-color:var(--background-color-accent-teal-hover);--hover-text-color:var(--text-color-accent-teal);--active-bg-color:var(--background-color-accent-teal-selected);--active-text-color:var(--text-color-accent-teal)}.nv-tag:where(.nv-tag--color-gray){--bg-color:var(--background-color-accent-gray-subtle);--border-color:var(--border-color-accent-gray);--text-color:var(--text-color-primary);--hover-bg-color:linear-gradient(0deg,var(--background-color-interaction-hover)0.0%,var(--background-color-interaction-hover)100.0%),var(--background-color-accent-gray-subtle);--hover-text-color:var(--text-color-primary);--active-bg-color:var(--background-color-accent-gray-subtle-selected);--active-text-color:var(--text-color-accent-white)}.nv-tag:where(.nv-tag--color-gray):where(.nv-tag--kind-outline){--bg-color:inherit;--border-color:var(--border-color-accent-gray);--text-color:var(--text-color-accent-gray);--hover-bg-color:var(--background-color-interaction-hover);--hover-text-color:var(--text-color-accent-gray);--active-bg-color:var(--background-color-accent-gray-selected);--active-text-color:var(--text-color-inverse)}.nv-tag:where(.nv-tag--selected,:has(:checked,[data-state=checked])){--bg-color:var(--active-bg-color);--text-color:var(--active-text-color);--hover-bg-color:oklch(from var(--active-bg-color)calc(l + .1)c h);--hover-text-color:var(--active-text-color)}.nv-text-area-root{--max-auto-height:400px}.nv-text-area-root .nv-text-area-element{resize:none;width:100%;min-width:100%;height:100%;min-height:3lh;font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);text-overflow:ellipsis;scrollbar-width:thin;scrollbar-color:var(--nv-scrollbar-color);flex:1}.nv-text-area-root .nv-text-area-element::placeholder{color:var(--text-color-placeholder)}.nv-text-area-root .nv-text-area-element:focus-visible{outline:none}.nv-text-area-root .nv-text-area-element.nv-text-area-element--resizeable-manual{resize:vertical}.nv-text-area-root .nv-text-area-element.nv-text-area-element--resizeable-auto{field-sizing:content;max-height:var(--max-auto-height,100.0%)}.nv-toast-root{--toast-icon-color:var(--text-color-feedback-info);height:calc(var(--spacing)*10);border-radius:var(--radius-md);border:1px solid;border-color:var(--border-color-base);background-color:var(--background-color-surface-overlay);width:100%;padding:calc(var(--spacing)*2);font-family:var(--font-sans);color:var(--text-color-primary);font-style:normal;font-weight:var(--font-weight-regular);box-shadow:var(--shadow-lg);justify-content:space-between;align-items:center;display:flex}.nv-toast-root.nv-toast-root--status-success{--toast-icon-color:var(--text-color-feedback-success)}.nv-toast-root.nv-toast-root--status-warning{--toast-icon-color:var(--text-color-feedback-warning)}.nv-toast-root.nv-toast-root--status-error{--toast-icon-color:var(--text-color-feedback-danger)}.nv-toast-root.nv-toast-root--status-info{--toast-icon-color:var(--text-color-feedback-info)}.nv-toast-root.nv-toast-root--status-neutral,.nv-toast-root.nv-toast-root--status-working{--toast-icon-color:var(--text-color-base)}.nv-toast-icon{color:var(--toast-icon-color);flex-shrink:0;place-items:center;display:grid}.nv-toast-text{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-14);line-height:var(--leading-lh-150);overflow:hidden}.nv-toast-content{align-items:center;gap:calc(var(--spacing)*2);text-overflow:ellipsis;white-space:nowrap;display:flex;overflow:hidden}.nv-toast-actions{align-items:center;gap:calc(var(--spacing)*2);display:flex}@keyframes nv-tooltip-in{0%{opacity:0;translate:var(--nv-tooltip-translate-start)}to{opacity:1;translate:0}}.nv-tooltip-content{--nv-tooltip-translate-start:0 4px;border-radius:var(--radius-md);border:1px solid;border-color:var(--border-color-component-tooltip);background-color:var(--background-color-component-tooltip);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-semibold);color:var(--text-color-accent-white);box-shadow:var(--shadow-md);z-index:1080;text-wrap:wrap;opacity:0;width:max-content;max-width:min(100vw - 16px,320px);translate:var(--nv-tooltip-translate-start);margin:auto;font-style:normal;position:fixed;inset:auto}@media (prefers-reduced-motion:no-preference){.nv-tooltip-content{transition:opacity .25s var(--ease-out),translate .25s var(--ease-out)}}@supports (position-anchor:--a){.nv-tooltip-content{--nv-tooltip-offset:4px;margin:var(--nv-tooltip-offset);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}.nv-tooltip-content[data-side=top]{--nv-tooltip-translate-start:0 4px;position-area:top}.nv-tooltip-content[data-side=top][data-align=start]{position-area:top span-right}.nv-tooltip-content[data-side=top][data-align=end]{position-area:top span-left}.nv-tooltip-content[data-side=bottom]{--nv-tooltip-translate-start:0 -4px;position-area:bottom}.nv-tooltip-content[data-side=bottom][data-align=start]{position-area:bottom span-right}.nv-tooltip-content[data-side=bottom][data-align=end]{position-area:bottom span-left}.nv-tooltip-content[data-side=left]{--nv-tooltip-translate-start:4px 0;position-area:left}.nv-tooltip-content[data-side=left][data-align=start]{position-area:left span-bottom}.nv-tooltip-content[data-side=left][data-align=end]{position-area:left span-top}.nv-tooltip-content[data-side=right]{--nv-tooltip-translate-start:-4px 0;position-area:right}.nv-tooltip-content[data-side=right][data-align=start]{position-area:right span-bottom}.nv-tooltip-content[data-side=right][data-align=end]{position-area:right span-top}}.nv-tooltip-trigger{anchor-name:var(--nv-anchor-self,--nv-anchor-none),var(--nv-anchor-tooltip,--nv-anchor-none)}:where(.nv-tooltip-trigger:not(.nv-icon):not(svg)){width:fit-content}:is(.nv-tooltip-content[data-state=open],.nv-tooltip-content:popover-open,.nv-tooltip-content.\:popover-open){opacity:1;display:block;translate:0}@media (prefers-reduced-motion:no-preference){:is(.nv-tooltip-content[data-state=open],.nv-tooltip-content:popover-open,.nv-tooltip-content.\:popover-open){animation:nv-tooltip-in .25s var(--ease-out)}}.nv-tooltip-content[data-state=closed]:not(:popover-open):not(.\:popover-open){opacity:0;translate:var(--nv-tooltip-translate-start)}.nv-tree-nav-branch>summary{list-style:none}.nv-tree-nav-branch>summary::-webkit-details-marker{display:none}.nv-tree-nav-branch>summary::marker{content:"";display:none}.nv-tree-nav-branch::details-content{height:0;transition:height .2s var(--ease-out),content-visibility .2s allow-discrete;display:block;overflow:clip}.nv-tree-nav-branch[open]::details-content{height:auto}@media (prefers-reduced-motion:reduce){.nv-tree-nav-branch::details-content{transition-duration:.01ms}}.nv-tree-nav-root{font-family:var(--font-sans);color:var(--text-color-primary);font-weight:var(--font-weight-regular);font-style:normal}.nv-tree-nav-root .nv-icon,.nv-tree-nav-root svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);color:var(--text-color-base);flex-shrink:0}.nv-tree-nav-root [data-disabled],.nv-tree-nav-root [aria-disabled=true]{color:var(--text-color-disabled)}:is(.nv-tree-nav-root [data-disabled],.nv-tree-nav-root [aria-disabled=true]) .nv-icon,:is(.nv-tree-nav-root [data-disabled],.nv-tree-nav-root [aria-disabled=true]) svg{color:var(--text-color-disabled)}.nv-tree-nav-list{margin:calc(var(--spacing)*0);padding:calc(var(--spacing)*0);list-style-type:none}.nv-tree-nav-list[aria-disabled=true]{pointer-events:none}.nv-tree-nav-list-item{content-visibility:auto;contain-intrinsic-size:auto 1rem}.nv-tree-nav-branch-trigger,.nv-tree-nav-leaf{cursor:pointer;font-size:var(--text-14);align-items:center;gap:var(--spacing);color:inherit;background:0 0;border:1px solid #0000;padding-inline-start:calc(var(--nv-tree-nav-depth,0)*24px + var(--spacing));padding-inline-end:var(--spacing);line-height:1.57143;text-decoration:none;display:flex}@media (prefers-reduced-motion:no-preference){:is(.nv-tree-nav-branch-trigger,.nv-tree-nav-leaf){transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke;transition-duration:.2s;transition-timing-function:var(--ease-out)}}:is(.nv-tree-nav-branch-trigger,.nv-tree-nav-leaf):hover:not([data-disabled]):not([aria-disabled=true]){background:var(--background-color-interaction-hover)}:is(.nv-tree-nav-branch-trigger,.nv-tree-nav-leaf):focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}:is(.nv-tree-nav-branch-trigger,.nv-tree-nav-leaf)[data-disabled],:is(.nv-tree-nav-branch-trigger,.nv-tree-nav-leaf)[aria-disabled=true]{pointer-events:none}.nv-tree-nav-branch-trigger[data-collapsible=false]{cursor:default}.nv-tree-nav-branch-trigger--active,.nv-tree-nav-leaf--active{background:var(--background-color-interaction-selected)}:is(.nv-tree-nav-branch-trigger--active,.nv-tree-nav-leaf--active):hover:not([data-disabled]):not([aria-disabled=true]){background:var(--background-color-interaction-selected);border-color:var(--border-color-interaction-hover)}details:not([open])>summary .nv-tree-nav-icon{rotate:-90deg}details[open]>summary .nv-tree-nav-icon{rotate:none}.nv-tree-nav-label{min-width:calc(var(--spacing)*0);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.nv-upload-trigger.nv-input-shell{text-align:center;line-height:1.14286;display:inline-block}.nv-upload-trigger.nv-input-shell.nv-upload-trigger--dragged-over:not(:has(input:disabled)){border-color:var(--border-color-interaction-hover);background-color:var(--background-color-interaction-hover)}.nv-upload-trigger.nv-input-shell:has(input:disabled) .nv-upload-trigger-anchor.nv-upload-trigger-anchor{color:inherit;cursor:inherit;background:0 0;text-decoration:none}.nv-upload-trigger.nv-input-shell ::file-selector-button{display:none}@media (scripting:enabled){.nv-upload-trigger.nv-input-shell .nv-upload-input-element{pointer-events:none;opacity:0;width:1px;height:1px;position:absolute}}.nv-upload-content{gap:calc(var(--spacing)*2);padding-top:calc(var(--spacing)*2);flex-direction:column;display:flex}.nv-upload-content.nv-upload-content--kind-media{flex-flow:wrap}.nv-upload-item-actions-group{justify-content:flex-end;gap:calc(var(--spacing)*1);flex-wrap:wrap;width:fit-content;display:flex}.nv-upload-description{justify-content:center;gap:calc(var(--spacing)*1);width:100%;padding-top:calc(var(--spacing)*1.5);font-family:var(--font-sans);font-size:var(--text-10);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular);flex-wrap:wrap;display:inline-flex}.nv-upload-item{--nv-input-height:auto;border-radius:var(--radius-md);border:1px solid;border-color:var(--border-color-base);background-color:var(--background-color-surface-raised);padding:calc(var(--spacing)*2);font-family:var(--font-sans);color:var(--text-color-primary);font-style:normal;font-weight:var(--font-weight-regular);display:flex;overflow:hidden}@media (hover:hover){.nv-upload-item:hover{border-color:var(--border-color-interaction-hover)}}@media (prefers-reduced-motion:no-preference){.nv-upload-item{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.25s;transition-timing-function:var(--ease-out)}}.nv-upload-item.nv-upload-item--status-error{--status-icon-color:var(--text-color-feedback-danger);border:1px solid;border-color:var(--border-color-feedback-danger)}@media (hover:hover){.nv-upload-item.nv-upload-item--status-error:hover{border-color:var(--border-color-feedback-danger-hover)}}.nv-upload-item.nv-upload-item--status-error .nv-upload-item-content-info{color:var(--text-color-feedback-danger-subtle)}.nv-upload-item svg:not(.nv-button svg),.nv-upload-item .nv-icon:not(.nv-button .nv-icon){color:var(--status-icon-color,var(--text-color-primary))}.nv-upload-item .nv-upload-item-hover-section{opacity:0}@media (prefers-reduced-motion:no-preference){.nv-upload-item .nv-upload-item-hover-section{transition:opacity .25s var(--ease-out)}}:is(.nv-upload-item:hover,.nv-upload-item:focus,.nv-upload-item:focus-within) .nv-upload-item-hover-section{opacity:1}.nv-upload-item .nv-upload-item-thumbnail{object-fit:cover}.nv-upload-item.nv-upload-item--kind-card{gap:calc(var(--spacing)*4);padding:calc(var(--spacing)*3)}.nv-upload-item.nv-upload-item--kind-card .nv-upload-item-thumbnail{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8);min-width:calc(var(--spacing)*8);place-items:center;display:grid}.nv-upload-item.nv-upload-item--kind-card .nv-upload-item-thumbnail.nv-upload-item-thumbnail-icon{border:1px solid;border-color:var(--border-color-base);color:var(--text-color-base)}.nv-upload-item.nv-upload-item--kind-card .nv-upload-item-thumbnail.nv-upload-item-thumbnail-icon:before{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.nv-upload-item.nv-upload-item--kind-media{justify-content:center;align-items:center;width:100px;height:100px;display:flex;position:relative}.nv-upload-item.nv-upload-item--kind-media .nv-upload-item-thumbnail{inset:calc(var(--spacing)*0);transition:opacity .25s var(--ease-out);position:absolute}:is(.nv-upload-item.nv-upload-item--kind-media:hover,.nv-upload-item.nv-upload-item--kind-media:focus,.nv-upload-item.nv-upload-item--kind-media:focus-within) .nv-upload-item-thumbnail{opacity:.1}.nv-upload-item-content{justify-content:space-between;gap:calc(var(--spacing)*2);flex-direction:column;flex:1;display:flex}.nv-upload-item-content-heading{font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-regular);line-height:1.28571}.nv-upload-item-content-info{align-items:center;gap:calc(var(--spacing)*1);font-family:var(--font-sans);font-size:var(--text-10);line-height:1.2;font-weight:var(--font-weight-regular);display:inline-flex}.nv-upload-item-content-info *{text-wrap:nowrap}.nv-upload-item-upload-spinner{animation:var(--animate-spin);font-size:var(--text-12)}.nv-upload-item-top-left{top:calc(var(--spacing)*2);left:calc(var(--spacing)*2);position:absolute}.nv-upload-item-top-right{top:calc(var(--spacing)*2);right:calc(var(--spacing)*2);position:absolute}.nv-vertical-nav-root{border-right:1px solid;border-color:var(--border-color-base);background-color:var(--background-color-surface-navigation);width:240px;height:100%;font-family:var(--font-sans);color:var(--text-color-primary)}.nv-vertical-nav-root .nv-vertical-nav-item-label{min-width:calc(var(--spacing)*0);text-overflow:ellipsis;white-space:nowrap;line-height:var(--leading-lh-125);flex:1;display:block;overflow:hidden}.nv-vertical-nav-root .nv-vertical-nav-list{margin:calc(var(--spacing)*0);width:100%;height:100%;padding:calc(var(--spacing)*1);scrollbar-width:thin;list-style-type:none;position:relative;overflow-y:auto}.nv-vertical-nav-root .nv-vertical-nav-sub-list{padding-right:calc(var(--spacing)*6);padding-left:calc(var(--spacing)*8);list-style-type:none}.nv-vertical-nav-root .nv-vertical-nav-item--active:not(.nv-vertical-nav-item--disabled):before{content:"";left:var(--spacing);top:var(--spacing);bottom:var(--spacing);width:calc(var(--spacing)/2);background-color:var(--color-brand);border-radius:var(--radius-sm);position:absolute}.nv-vertical-nav-root .nv-vertical-nav-item{cursor:pointer;align-items:center;gap:calc(var(--spacing)*2);border-radius:var(--radius-sm);width:100%;max-width:100%;padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*4);text-align:start;font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-bold);line-height:var(--leading-lh-100);display:flex;position:relative;overflow:hidden}.nv-vertical-nav-root .nv-vertical-nav-item.nv-vertical-nav-item--active:not(.nv-vertical-nav-item--disabled){background-color:var(--background-color-interaction-pressed)}.nv-vertical-nav-root .nv-vertical-nav-item:hover:not(.nv-vertical-nav-item--disabled):not(:disabled){background-color:var(--background-color-interaction-hover)}.nv-vertical-nav-root .nv-vertical-nav-item:active:not(.nv-vertical-nav-item--disabled):not(:disabled){background-color:var(--background-color-interaction-pressed)}.nv-vertical-nav-root .nv-vertical-nav-item:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}.nv-vertical-nav-root .nv-vertical-nav-item--kind-secondary{height:calc(var(--spacing)*10);align-items:center;gap:calc(var(--spacing)*2);border-radius:var(--radius-sm);width:100%;max-width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-regular);color:var(--text-color-secondary);line-height:var(--leading-lh-100);display:flex;position:relative}.nv-vertical-nav-root .nv-vertical-nav-item--kind-secondary:hover:not(.nv-vertical-nav-item--disabled){background-color:var(--background-color-interaction-hover)}.nv-vertical-nav-root .nv-vertical-nav-item--kind-secondary:active:not(.nv-vertical-nav-item--disabled){background-color:var(--background-color-interaction-pressed)}.nv-vertical-nav-root .nv-vertical-nav-item--kind-secondary.nv-vertical-nav-item--active:not(.nv-vertical-nav-item--disabled){background-color:var(--background-color-interaction-pressed);color:var(--text-color-primary);font-weight:var(--font-weight-bold)}.nv-vertical-nav-root .nv-vertical-nav-item--disabled{cursor:not-allowed;color:var(--text-color-disabled)}.nv-vertical-nav-root .nv-vertical-nav-item svg,.nv-vertical-nav-root .nv-vertical-nav-item .nv-icon{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);flex-shrink:0}.nv-vertical-nav-root .nv-vertical-nav-collapsible-trigger .nv-animated-chevron{margin-left:auto}.nv-vertical-nav-root .nv-vertical-nav-collapsible-trigger{border-radius:var(--radius-sm);max-width:100%;display:flex;overflow:hidden}.nv-vertical-nav-root .nv-vertical-nav-collapsible-trigger:focus-visible{outline:2px solid var(--nv-focus-outline-color,currentColor);outline-offset:-2px}.nv-vertical-nav-root .nv-collapsible-trigger[data-disabled]{opacity:1}.nv-vertical-nav-root .nv-vertical-nav-collapsible-section:not([open]):has(.nv-vertical-nav-item--kind-secondary.nv-vertical-nav-item--active:not(.nv-vertical-nav-item--disabled)):not([data-disabled]) .nv-vertical-nav-collapsible-trigger{position:relative}.nv-vertical-nav-root .nv-vertical-nav-collapsible-section:not([open]):has(.nv-vertical-nav-item--kind-secondary.nv-vertical-nav-item--active:not(.nv-vertical-nav-item--disabled)):not([data-disabled]) .nv-vertical-nav-collapsible-trigger:before{content:"";left:var(--spacing);top:var(--spacing);bottom:var(--spacing);width:calc(var(--spacing)/2);background-color:var(--color-brand);border-radius:var(--radius-sm);position:absolute}.nv-vertical-nav-root .nv-vertical-nav-collapsible-section:not([open]):not([data-disabled]):has(.nv-vertical-nav-item--kind-secondary.nv-vertical-nav-item--active:not(.nv-vertical-nav-item--disabled)) .nv-vertical-nav-collapsible-trigger{background-color:var(--background-color-interaction-pressed)}.nv-vertical-nav-root .nv-vertical-nav-collapsible-section[data-disabled] .nv-vertical-nav-item--kind-secondary{cursor:not-allowed;color:var(--text-color-disabled)}@media (prefers-reduced-motion:no-preference){:is(.nv-vertical-nav-root .nv-vertical-nav-item,.nv-vertical-nav-root .nv-vertical-nav-collapsible-section:not([data-disabled])){transition:background-color .2s var(--ease-out),color .2s var(--ease-out)}}.nv-text--body-bold-2xl{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-text--body-bold-3xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-text--body-bold-lg{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-text--body-bold-md{font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-text--body-bold-sm{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-text--body-bold-xl{font-family:var(--font-sans);font-size:var(--text-18);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-text--body-bold-xs{font-family:var(--font-sans);font-size:var(--text-10);line-height:var(--leading-lh-150);font-weight:var(--font-weight-bold)}.nv-text--body-regular-2xl{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--body-regular-3xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--body-regular-lg{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--body-regular-md{font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--body-regular-sm{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--body-regular-xl{font-family:var(--font-sans);font-size:var(--text-18);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--body-regular-xs{font-family:var(--font-sans);font-size:var(--text-10);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--body-semibold-2xl{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-150);font-weight:var(--font-weight-semibold)}.nv-text--body-semibold-3xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-150);font-weight:var(--font-weight-semibold)}.nv-text--body-semibold-lg{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-150);font-weight:var(--font-weight-semibold)}.nv-text--body-semibold-md{font-family:var(--font-sans);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-semibold)}.nv-text--body-semibold-sm{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-150);font-weight:var(--font-weight-semibold)}.nv-text--body-semibold-xl{font-family:var(--font-sans);font-size:var(--text-18);line-height:var(--leading-lh-150);font-weight:var(--font-weight-semibold)}.nv-text--body-semibold-xs{font-family:var(--font-sans);font-size:var(--text-10);line-height:var(--leading-lh-150);font-weight:var(--font-weight-semibold)}.nv-text--display-2xl{font-family:var(--font-sans);font-size:var(--text-64);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--display-lg{font-family:var(--font-sans);font-size:var(--text-50);line-height:1.24;font-weight:var(--font-weight-bold)}.nv-text--display-md{font-family:var(--font-sans);font-size:var(--text-44);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--display-sm{font-family:var(--font-sans);font-size:var(--text-40);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--display-xl{font-family:var(--font-sans);font-size:var(--text-56);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--display-xs{font-family:var(--font-sans);font-size:var(--text-36);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--label-bold-2xl{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--label-bold-3xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--label-bold-lg{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--label-bold-md{font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-bold)}.nv-text--label-bold-sm{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--label-bold-xl{font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-bold)}.nv-text--label-bold-xs{font-family:var(--font-sans);font-size:var(--text-10);line-height:1.2;font-weight:var(--font-weight-bold)}.nv-text--label-light-2xl{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-125);font-weight:var(--font-weight-light)}.nv-text--label-light-3xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-125);font-weight:var(--font-weight-light)}.nv-text--label-light-lg{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-125);font-weight:var(--font-weight-light)}.nv-text--label-light-md{font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-light)}.nv-text--label-light-sm{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-light)}.nv-text--label-light-xl{font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-light)}.nv-text--label-light-xs{font-family:var(--font-sans);font-size:var(--text-10);line-height:1.2;font-weight:var(--font-weight-light)}.nv-text--label-regular-2xl{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular)}.nv-text--label-regular-3xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular)}.nv-text--label-regular-lg{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular)}.nv-text--label-regular-md{font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-regular)}.nv-text--label-regular-sm{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-regular)}.nv-text--label-regular-xl{font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-regular)}.nv-text--label-regular-xs{font-family:var(--font-sans);font-size:var(--text-10);line-height:1.2;font-weight:var(--font-weight-regular)}.nv-text--label-semibold-2xl{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-125);font-weight:var(--font-weight-semibold)}.nv-text--label-semibold-3xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-125);font-weight:var(--font-weight-semibold)}.nv-text--label-semibold-lg{font-family:var(--font-sans);font-size:var(--text-16);line-height:var(--leading-lh-125);font-weight:var(--font-weight-semibold)}.nv-text--label-semibold-md{font-family:var(--font-sans);font-size:var(--text-14);line-height:1.21429;font-weight:var(--font-weight-semibold)}.nv-text--label-semibold-sm{font-family:var(--font-sans);font-size:var(--text-12);line-height:var(--leading-lh-125);font-weight:var(--font-weight-semibold)}.nv-text--label-semibold-xl{font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-semibold)}.nv-text--label-semibold-xs{font-family:var(--font-sans);font-size:var(--text-10);line-height:1.2;font-weight:var(--font-weight-semibold)}.nv-text--mono-2xl{font-family:var(--font-mono);font-size:var(--text-24);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--mono-lg{font-family:var(--font-mono);font-size:var(--text-16);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--mono-md{font-family:var(--font-mono);font-size:var(--text-14);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--mono-sm{font-family:var(--font-mono);font-size:var(--text-12);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--mono-xl{font-family:var(--font-mono);font-size:var(--text-20);line-height:var(--leading-lh-150);font-weight:var(--font-weight-regular)}.nv-text--title-2xl{font-family:var(--font-sans);font-size:var(--text-36);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--title-lg{font-family:var(--font-sans);font-size:var(--text-28);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--title-md{font-family:var(--font-sans);font-size:var(--text-24);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--title-sm{font-family:var(--font-sans);font-size:var(--text-20);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--title-xl{font-family:var(--font-sans);font-size:var(--text-32);line-height:var(--leading-lh-125);font-weight:var(--font-weight-bold)}.nv-text--title-xs{font-family:var(--font-sans);font-size:var(--text-18);line-height:1.22222;font-weight:var(--font-weight-bold)}.nv-text--font-mono{font-family:var(--font-mono)}.nv-text--font-sans{font-family:var(--font-sans)}.nv-text--weight-light{font-weight:var(--font-weight-light)}.nv-text--weight-regular{font-weight:var(--font-weight-regular)}.nv-text--weight-semibold{font-weight:var(--font-weight-semibold)}.nv-text--weight-bold{font-weight:var(--font-weight-bold)}.nv-text--style-italic{font-style:italic}.nv-text--style-normal{font-style:normal}.nv-text--size-10{font-size:var(--text-10)}.nv-text--size-12{font-size:var(--text-12)}.nv-text--size-14{font-size:var(--text-14)}.nv-text--size-16{font-size:var(--text-16)}.nv-text--size-18{font-size:var(--text-18)}.nv-text--size-20{font-size:var(--text-20)}.nv-text--size-22{font-size:var(--text-22)}.nv-text--size-24{font-size:var(--text-24)}.nv-text--size-28{font-size:var(--text-28)}.nv-text--size-32{font-size:var(--text-32)}.nv-text--size-36{font-size:var(--text-36)}.nv-text--size-40{font-size:var(--text-40)}.nv-text--size-44{font-size:var(--text-44)}.nv-text--size-48{font-size:var(--text-48)}.nv-text--size-50{font-size:var(--text-50)}.nv-text--size-56{font-size:var(--text-56)}.nv-text--size-60{font-size:var(--text-60)}.nv-text--size-64{font-size:var(--text-64)}.nv-text--size-72{font-size:var(--text-72)}.nv-text--size-80{font-size:var(--text-80)}.nv-text--underline{text-decoration-line:underline}.nv-text--line-height-100{line-height:var(--leading-lh-100)}.nv-text--line-height-125{line-height:var(--leading-lh-125)}.nv-text--line-height-150{line-height:var(--leading-lh-150)}.nv-text--line-height-175{line-height:var(--leading-lh-175)}.nv-primitive--spacing-0{padding:calc(var(--spacing)*0)}.nv-primitive--spacing-0_25{padding:calc(var(--spacing)*.25)}.nv-primitive--spacing-0_5{padding:calc(var(--spacing)*.5)}.nv-primitive--spacing-0_75{padding:calc(var(--spacing)*.75)}.nv-primitive--spacing-1{padding:calc(var(--spacing)*1)}.nv-primitive--spacing-1_5{padding:calc(var(--spacing)*1.5)}.nv-primitive--spacing-2{padding:calc(var(--spacing)*2)}.nv-primitive--spacing-2_5{padding:calc(var(--spacing)*2.5)}.nv-primitive--spacing-3{padding:calc(var(--spacing)*3)}.nv-primitive--spacing-3_5{padding:calc(var(--spacing)*3.5)}.nv-primitive--spacing-4{padding:calc(var(--spacing)*4)}.nv-primitive--spacing-5{padding:calc(var(--spacing)*5)}.nv-primitive--spacing-6{padding:calc(var(--spacing)*6)}.nv-primitive--spacing-7{padding:calc(var(--spacing)*7)}.nv-primitive--spacing-8{padding:calc(var(--spacing)*8)}.nv-primitive--spacing-9{padding:calc(var(--spacing)*9)}.nv-primitive--spacing-10{padding:calc(var(--spacing)*10)}.nv-primitive--spacing-11{padding:calc(var(--spacing)*11)}.nv-primitive--spacing-12{padding:calc(var(--spacing)*12)}.nv-primitive--spacing-14{padding:calc(var(--spacing)*14)}.nv-primitive--spacing-16{padding:calc(var(--spacing)*16)}.nv-primitive--spacing-18{padding:calc(var(--spacing)*18)}.nv-primitive--spacing-20{padding:calc(var(--spacing)*20)}.nv-primitive--spacing-24{padding:calc(var(--spacing)*24)}.nv-primitive--spacing-28{padding:calc(var(--spacing)*28)}.nv-primitive--spacing-32{padding:calc(var(--spacing)*32)}.nv-primitive--spacing-36{padding:calc(var(--spacing)*36)}.nv-primitive--spacing-40{padding:calc(var(--spacing)*40)}.nv-primitive--spacing-44{padding:calc(var(--spacing)*44)}.nv-primitive--spacing-48{padding:calc(var(--spacing)*48)}.nv-primitive--spacing-52{padding:calc(var(--spacing)*52)}.nv-primitive--spacing-56{padding:calc(var(--spacing)*56)}.nv-primitive--spacing-60{padding:calc(var(--spacing)*60)}.nv-primitive--spacing-64{padding:calc(var(--spacing)*64)}.nv-primitive--spacing-72{padding:calc(var(--spacing)*72)}.nv-primitive--spacing-80{padding:calc(var(--spacing)*80)}.nv-primitive--spacing-96{padding:calc(var(--spacing)*96)}.nv-primitive--spacing-250{padding:calc(var(--spacing)*250)}.nv-primitive--spacing-px{padding:1px}.nv-primitive--spacing-density-xxs{padding:var(--spacing-density-xxs)}.nv-primitive--spacing-density-xs{padding:var(--spacing-density-xs)}.nv-primitive--spacing-density-sm{padding:var(--spacing-density-sm)}.nv-primitive--spacing-density-md{padding:var(--spacing-density-md)}.nv-primitive--spacing-density-lg{padding:var(--spacing-density-lg)}.nv-primitive--spacing-density-xl{padding:var(--spacing-density-xl)}.nv-primitive--spacing-density-2xl{padding:var(--spacing-density-2xl)}.nv-primitive--spacing-density-3xl{padding:var(--spacing-density-3xl)}.nv-primitive--spacing-density-4xl{padding:var(--spacing-density-4xl)}.nv-primitive--spacing-density-5xl{padding:var(--spacing-density-5xl)}.nv-primitive--spacing-inherit{padding:inherit}.nv-primitive--spacing-x-0{padding-inline:calc(var(--spacing)*0)}.nv-primitive--spacing-x-0_25{padding-inline:calc(var(--spacing)*.25)}.nv-primitive--spacing-x-0_5{padding-inline:calc(var(--spacing)*.5)}.nv-primitive--spacing-x-0_75{padding-inline:calc(var(--spacing)*.75)}.nv-primitive--spacing-x-1{padding-inline:calc(var(--spacing)*1)}.nv-primitive--spacing-x-1_5{padding-inline:calc(var(--spacing)*1.5)}.nv-primitive--spacing-x-2{padding-inline:calc(var(--spacing)*2)}.nv-primitive--spacing-x-2_5{padding-inline:calc(var(--spacing)*2.5)}.nv-primitive--spacing-x-3{padding-inline:calc(var(--spacing)*3)}.nv-primitive--spacing-x-3_5{padding-inline:calc(var(--spacing)*3.5)}.nv-primitive--spacing-x-4{padding-inline:calc(var(--spacing)*4)}.nv-primitive--spacing-x-5{padding-inline:calc(var(--spacing)*5)}.nv-primitive--spacing-x-6{padding-inline:calc(var(--spacing)*6)}.nv-primitive--spacing-x-7{padding-inline:calc(var(--spacing)*7)}.nv-primitive--spacing-x-8{padding-inline:calc(var(--spacing)*8)}.nv-primitive--spacing-x-9{padding-inline:calc(var(--spacing)*9)}.nv-primitive--spacing-x-10{padding-inline:calc(var(--spacing)*10)}.nv-primitive--spacing-x-11{padding-inline:calc(var(--spacing)*11)}.nv-primitive--spacing-x-12{padding-inline:calc(var(--spacing)*12)}.nv-primitive--spacing-x-14{padding-inline:calc(var(--spacing)*14)}.nv-primitive--spacing-x-16{padding-inline:calc(var(--spacing)*16)}.nv-primitive--spacing-x-18{padding-inline:calc(var(--spacing)*18)}.nv-primitive--spacing-x-20{padding-inline:calc(var(--spacing)*20)}.nv-primitive--spacing-x-24{padding-inline:calc(var(--spacing)*24)}.nv-primitive--spacing-x-28{padding-inline:calc(var(--spacing)*28)}.nv-primitive--spacing-x-32{padding-inline:calc(var(--spacing)*32)}.nv-primitive--spacing-x-36{padding-inline:calc(var(--spacing)*36)}.nv-primitive--spacing-x-40{padding-inline:calc(var(--spacing)*40)}.nv-primitive--spacing-x-44{padding-inline:calc(var(--spacing)*44)}.nv-primitive--spacing-x-48{padding-inline:calc(var(--spacing)*48)}.nv-primitive--spacing-x-52{padding-inline:calc(var(--spacing)*52)}.nv-primitive--spacing-x-56{padding-inline:calc(var(--spacing)*56)}.nv-primitive--spacing-x-60{padding-inline:calc(var(--spacing)*60)}.nv-primitive--spacing-x-64{padding-inline:calc(var(--spacing)*64)}.nv-primitive--spacing-x-72{padding-inline:calc(var(--spacing)*72)}.nv-primitive--spacing-x-80{padding-inline:calc(var(--spacing)*80)}.nv-primitive--spacing-x-96{padding-inline:calc(var(--spacing)*96)}.nv-primitive--spacing-x-250{padding-inline:calc(var(--spacing)*250)}.nv-primitive--spacing-x-px{padding-inline:1px}.nv-primitive--spacing-x-density-xxs{padding-inline:var(--spacing-density-xxs)}.nv-primitive--spacing-x-density-xs{padding-inline:var(--spacing-density-xs)}.nv-primitive--spacing-x-density-sm{padding-inline:var(--spacing-density-sm)}.nv-primitive--spacing-x-density-md{padding-inline:var(--spacing-density-md)}.nv-primitive--spacing-x-density-lg{padding-inline:var(--spacing-density-lg)}.nv-primitive--spacing-x-density-xl{padding-inline:var(--spacing-density-xl)}.nv-primitive--spacing-x-density-2xl{padding-inline:var(--spacing-density-2xl)}.nv-primitive--spacing-x-density-3xl{padding-inline:var(--spacing-density-3xl)}.nv-primitive--spacing-x-density-4xl{padding-inline:var(--spacing-density-4xl)}.nv-primitive--spacing-x-density-5xl{padding-inline:var(--spacing-density-5xl)}.nv-primitive--spacing-x-inherit{padding-inline:inherit}.nv-primitive--spacing-y-0{padding-block:calc(var(--spacing)*0)}.nv-primitive--spacing-y-0_25{padding-block:calc(var(--spacing)*.25)}.nv-primitive--spacing-y-0_5{padding-block:calc(var(--spacing)*.5)}.nv-primitive--spacing-y-0_75{padding-block:calc(var(--spacing)*.75)}.nv-primitive--spacing-y-1{padding-block:calc(var(--spacing)*1)}.nv-primitive--spacing-y-1_5{padding-block:calc(var(--spacing)*1.5)}.nv-primitive--spacing-y-2{padding-block:calc(var(--spacing)*2)}.nv-primitive--spacing-y-2_5{padding-block:calc(var(--spacing)*2.5)}.nv-primitive--spacing-y-3{padding-block:calc(var(--spacing)*3)}.nv-primitive--spacing-y-3_5{padding-block:calc(var(--spacing)*3.5)}.nv-primitive--spacing-y-4{padding-block:calc(var(--spacing)*4)}.nv-primitive--spacing-y-5{padding-block:calc(var(--spacing)*5)}.nv-primitive--spacing-y-6{padding-block:calc(var(--spacing)*6)}.nv-primitive--spacing-y-7{padding-block:calc(var(--spacing)*7)}.nv-primitive--spacing-y-8{padding-block:calc(var(--spacing)*8)}.nv-primitive--spacing-y-9{padding-block:calc(var(--spacing)*9)}.nv-primitive--spacing-y-10{padding-block:calc(var(--spacing)*10)}.nv-primitive--spacing-y-11{padding-block:calc(var(--spacing)*11)}.nv-primitive--spacing-y-12{padding-block:calc(var(--spacing)*12)}.nv-primitive--spacing-y-14{padding-block:calc(var(--spacing)*14)}.nv-primitive--spacing-y-16{padding-block:calc(var(--spacing)*16)}.nv-primitive--spacing-y-18{padding-block:calc(var(--spacing)*18)}.nv-primitive--spacing-y-20{padding-block:calc(var(--spacing)*20)}.nv-primitive--spacing-y-24{padding-block:calc(var(--spacing)*24)}.nv-primitive--spacing-y-28{padding-block:calc(var(--spacing)*28)}.nv-primitive--spacing-y-32{padding-block:calc(var(--spacing)*32)}.nv-primitive--spacing-y-36{padding-block:calc(var(--spacing)*36)}.nv-primitive--spacing-y-40{padding-block:calc(var(--spacing)*40)}.nv-primitive--spacing-y-44{padding-block:calc(var(--spacing)*44)}.nv-primitive--spacing-y-48{padding-block:calc(var(--spacing)*48)}.nv-primitive--spacing-y-52{padding-block:calc(var(--spacing)*52)}.nv-primitive--spacing-y-56{padding-block:calc(var(--spacing)*56)}.nv-primitive--spacing-y-60{padding-block:calc(var(--spacing)*60)}.nv-primitive--spacing-y-64{padding-block:calc(var(--spacing)*64)}.nv-primitive--spacing-y-72{padding-block:calc(var(--spacing)*72)}.nv-primitive--spacing-y-80{padding-block:calc(var(--spacing)*80)}.nv-primitive--spacing-y-96{padding-block:calc(var(--spacing)*96)}.nv-primitive--spacing-y-250{padding-block:calc(var(--spacing)*250)}.nv-primitive--spacing-y-px{padding-block:1px}.nv-primitive--spacing-y-density-xxs{padding-block:var(--spacing-density-xxs)}.nv-primitive--spacing-y-density-xs{padding-block:var(--spacing-density-xs)}.nv-primitive--spacing-y-density-sm{padding-block:var(--spacing-density-sm)}.nv-primitive--spacing-y-density-md{padding-block:var(--spacing-density-md)}.nv-primitive--spacing-y-density-lg{padding-block:var(--spacing-density-lg)}.nv-primitive--spacing-y-density-xl{padding-block:var(--spacing-density-xl)}.nv-primitive--spacing-y-density-2xl{padding-block:var(--spacing-density-2xl)}.nv-primitive--spacing-y-density-3xl{padding-block:var(--spacing-density-3xl)}.nv-primitive--spacing-y-density-4xl{padding-block:var(--spacing-density-4xl)}.nv-primitive--spacing-y-density-5xl{padding-block:var(--spacing-density-5xl)}.nv-primitive--spacing-y-inherit{padding-block:inherit}.nv-primitive--spacing-t-0{padding-top:calc(var(--spacing)*0)}.nv-primitive--spacing-t-0_25{padding-top:calc(var(--spacing)*.25)}.nv-primitive--spacing-t-0_5{padding-top:calc(var(--spacing)*.5)}.nv-primitive--spacing-t-0_75{padding-top:calc(var(--spacing)*.75)}.nv-primitive--spacing-t-1{padding-top:calc(var(--spacing)*1)}.nv-primitive--spacing-t-1_5{padding-top:calc(var(--spacing)*1.5)}.nv-primitive--spacing-t-2{padding-top:calc(var(--spacing)*2)}.nv-primitive--spacing-t-2_5{padding-top:calc(var(--spacing)*2.5)}.nv-primitive--spacing-t-3{padding-top:calc(var(--spacing)*3)}.nv-primitive--spacing-t-3_5{padding-top:calc(var(--spacing)*3.5)}.nv-primitive--spacing-t-4{padding-top:calc(var(--spacing)*4)}.nv-primitive--spacing-t-5{padding-top:calc(var(--spacing)*5)}.nv-primitive--spacing-t-6{padding-top:calc(var(--spacing)*6)}.nv-primitive--spacing-t-7{padding-top:calc(var(--spacing)*7)}.nv-primitive--spacing-t-8{padding-top:calc(var(--spacing)*8)}.nv-primitive--spacing-t-9{padding-top:calc(var(--spacing)*9)}.nv-primitive--spacing-t-10{padding-top:calc(var(--spacing)*10)}.nv-primitive--spacing-t-11{padding-top:calc(var(--spacing)*11)}.nv-primitive--spacing-t-12{padding-top:calc(var(--spacing)*12)}.nv-primitive--spacing-t-14{padding-top:calc(var(--spacing)*14)}.nv-primitive--spacing-t-16{padding-top:calc(var(--spacing)*16)}.nv-primitive--spacing-t-18{padding-top:calc(var(--spacing)*18)}.nv-primitive--spacing-t-20{padding-top:calc(var(--spacing)*20)}.nv-primitive--spacing-t-24{padding-top:calc(var(--spacing)*24)}.nv-primitive--spacing-t-28{padding-top:calc(var(--spacing)*28)}.nv-primitive--spacing-t-32{padding-top:calc(var(--spacing)*32)}.nv-primitive--spacing-t-36{padding-top:calc(var(--spacing)*36)}.nv-primitive--spacing-t-40{padding-top:calc(var(--spacing)*40)}.nv-primitive--spacing-t-44{padding-top:calc(var(--spacing)*44)}.nv-primitive--spacing-t-48{padding-top:calc(var(--spacing)*48)}.nv-primitive--spacing-t-52{padding-top:calc(var(--spacing)*52)}.nv-primitive--spacing-t-56{padding-top:calc(var(--spacing)*56)}.nv-primitive--spacing-t-60{padding-top:calc(var(--spacing)*60)}.nv-primitive--spacing-t-64{padding-top:calc(var(--spacing)*64)}.nv-primitive--spacing-t-72{padding-top:calc(var(--spacing)*72)}.nv-primitive--spacing-t-80{padding-top:calc(var(--spacing)*80)}.nv-primitive--spacing-t-96{padding-top:calc(var(--spacing)*96)}.nv-primitive--spacing-t-250{padding-top:calc(var(--spacing)*250)}.nv-primitive--spacing-t-px{padding-top:1px}.nv-primitive--spacing-t-density-xxs{padding-top:var(--spacing-density-xxs)}.nv-primitive--spacing-t-density-xs{padding-top:var(--spacing-density-xs)}.nv-primitive--spacing-t-density-sm{padding-top:var(--spacing-density-sm)}.nv-primitive--spacing-t-density-md{padding-top:var(--spacing-density-md)}.nv-primitive--spacing-t-density-lg{padding-top:var(--spacing-density-lg)}.nv-primitive--spacing-t-density-xl{padding-top:var(--spacing-density-xl)}.nv-primitive--spacing-t-density-2xl{padding-top:var(--spacing-density-2xl)}.nv-primitive--spacing-t-density-3xl{padding-top:var(--spacing-density-3xl)}.nv-primitive--spacing-t-density-4xl{padding-top:var(--spacing-density-4xl)}.nv-primitive--spacing-t-density-5xl{padding-top:var(--spacing-density-5xl)}.nv-primitive--spacing-t-inherit{padding-top:inherit}.nv-primitive--spacing-r-0{padding-right:calc(var(--spacing)*0)}.nv-primitive--spacing-r-0_25{padding-right:calc(var(--spacing)*.25)}.nv-primitive--spacing-r-0_5{padding-right:calc(var(--spacing)*.5)}.nv-primitive--spacing-r-0_75{padding-right:calc(var(--spacing)*.75)}.nv-primitive--spacing-r-1{padding-right:calc(var(--spacing)*1)}.nv-primitive--spacing-r-1_5{padding-right:calc(var(--spacing)*1.5)}.nv-primitive--spacing-r-2{padding-right:calc(var(--spacing)*2)}.nv-primitive--spacing-r-2_5{padding-right:calc(var(--spacing)*2.5)}.nv-primitive--spacing-r-3{padding-right:calc(var(--spacing)*3)}.nv-primitive--spacing-r-3_5{padding-right:calc(var(--spacing)*3.5)}.nv-primitive--spacing-r-4{padding-right:calc(var(--spacing)*4)}.nv-primitive--spacing-r-5{padding-right:calc(var(--spacing)*5)}.nv-primitive--spacing-r-6{padding-right:calc(var(--spacing)*6)}.nv-primitive--spacing-r-7{padding-right:calc(var(--spacing)*7)}.nv-primitive--spacing-r-8{padding-right:calc(var(--spacing)*8)}.nv-primitive--spacing-r-9{padding-right:calc(var(--spacing)*9)}.nv-primitive--spacing-r-10{padding-right:calc(var(--spacing)*10)}.nv-primitive--spacing-r-11{padding-right:calc(var(--spacing)*11)}.nv-primitive--spacing-r-12{padding-right:calc(var(--spacing)*12)}.nv-primitive--spacing-r-14{padding-right:calc(var(--spacing)*14)}.nv-primitive--spacing-r-16{padding-right:calc(var(--spacing)*16)}.nv-primitive--spacing-r-18{padding-right:calc(var(--spacing)*18)}.nv-primitive--spacing-r-20{padding-right:calc(var(--spacing)*20)}.nv-primitive--spacing-r-24{padding-right:calc(var(--spacing)*24)}.nv-primitive--spacing-r-28{padding-right:calc(var(--spacing)*28)}.nv-primitive--spacing-r-32{padding-right:calc(var(--spacing)*32)}.nv-primitive--spacing-r-36{padding-right:calc(var(--spacing)*36)}.nv-primitive--spacing-r-40{padding-right:calc(var(--spacing)*40)}.nv-primitive--spacing-r-44{padding-right:calc(var(--spacing)*44)}.nv-primitive--spacing-r-48{padding-right:calc(var(--spacing)*48)}.nv-primitive--spacing-r-52{padding-right:calc(var(--spacing)*52)}.nv-primitive--spacing-r-56{padding-right:calc(var(--spacing)*56)}.nv-primitive--spacing-r-60{padding-right:calc(var(--spacing)*60)}.nv-primitive--spacing-r-64{padding-right:calc(var(--spacing)*64)}.nv-primitive--spacing-r-72{padding-right:calc(var(--spacing)*72)}.nv-primitive--spacing-r-80{padding-right:calc(var(--spacing)*80)}.nv-primitive--spacing-r-96{padding-right:calc(var(--spacing)*96)}.nv-primitive--spacing-r-250{padding-right:calc(var(--spacing)*250)}.nv-primitive--spacing-r-px{padding-right:1px}.nv-primitive--spacing-r-density-xxs{padding-right:var(--spacing-density-xxs)}.nv-primitive--spacing-r-density-xs{padding-right:var(--spacing-density-xs)}.nv-primitive--spacing-r-density-sm{padding-right:var(--spacing-density-sm)}.nv-primitive--spacing-r-density-md{padding-right:var(--spacing-density-md)}.nv-primitive--spacing-r-density-lg{padding-right:var(--spacing-density-lg)}.nv-primitive--spacing-r-density-xl{padding-right:var(--spacing-density-xl)}.nv-primitive--spacing-r-density-2xl{padding-right:var(--spacing-density-2xl)}.nv-primitive--spacing-r-density-3xl{padding-right:var(--spacing-density-3xl)}.nv-primitive--spacing-r-density-4xl{padding-right:var(--spacing-density-4xl)}.nv-primitive--spacing-r-density-5xl{padding-right:var(--spacing-density-5xl)}.nv-primitive--spacing-r-inherit{padding-right:inherit}.nv-primitive--spacing-b-0{padding-bottom:calc(var(--spacing)*0)}.nv-primitive--spacing-b-0_25{padding-bottom:calc(var(--spacing)*.25)}.nv-primitive--spacing-b-0_5{padding-bottom:calc(var(--spacing)*.5)}.nv-primitive--spacing-b-0_75{padding-bottom:calc(var(--spacing)*.75)}.nv-primitive--spacing-b-1{padding-bottom:calc(var(--spacing)*1)}.nv-primitive--spacing-b-1_5{padding-bottom:calc(var(--spacing)*1.5)}.nv-primitive--spacing-b-2{padding-bottom:calc(var(--spacing)*2)}.nv-primitive--spacing-b-2_5{padding-bottom:calc(var(--spacing)*2.5)}.nv-primitive--spacing-b-3{padding-bottom:calc(var(--spacing)*3)}.nv-primitive--spacing-b-3_5{padding-bottom:calc(var(--spacing)*3.5)}.nv-primitive--spacing-b-4{padding-bottom:calc(var(--spacing)*4)}.nv-primitive--spacing-b-5{padding-bottom:calc(var(--spacing)*5)}.nv-primitive--spacing-b-6{padding-bottom:calc(var(--spacing)*6)}.nv-primitive--spacing-b-7{padding-bottom:calc(var(--spacing)*7)}.nv-primitive--spacing-b-8{padding-bottom:calc(var(--spacing)*8)}.nv-primitive--spacing-b-9{padding-bottom:calc(var(--spacing)*9)}.nv-primitive--spacing-b-10{padding-bottom:calc(var(--spacing)*10)}.nv-primitive--spacing-b-11{padding-bottom:calc(var(--spacing)*11)}.nv-primitive--spacing-b-12{padding-bottom:calc(var(--spacing)*12)}.nv-primitive--spacing-b-14{padding-bottom:calc(var(--spacing)*14)}.nv-primitive--spacing-b-16{padding-bottom:calc(var(--spacing)*16)}.nv-primitive--spacing-b-18{padding-bottom:calc(var(--spacing)*18)}.nv-primitive--spacing-b-20{padding-bottom:calc(var(--spacing)*20)}.nv-primitive--spacing-b-24{padding-bottom:calc(var(--spacing)*24)}.nv-primitive--spacing-b-28{padding-bottom:calc(var(--spacing)*28)}.nv-primitive--spacing-b-32{padding-bottom:calc(var(--spacing)*32)}.nv-primitive--spacing-b-36{padding-bottom:calc(var(--spacing)*36)}.nv-primitive--spacing-b-40{padding-bottom:calc(var(--spacing)*40)}.nv-primitive--spacing-b-44{padding-bottom:calc(var(--spacing)*44)}.nv-primitive--spacing-b-48{padding-bottom:calc(var(--spacing)*48)}.nv-primitive--spacing-b-52{padding-bottom:calc(var(--spacing)*52)}.nv-primitive--spacing-b-56{padding-bottom:calc(var(--spacing)*56)}.nv-primitive--spacing-b-60{padding-bottom:calc(var(--spacing)*60)}.nv-primitive--spacing-b-64{padding-bottom:calc(var(--spacing)*64)}.nv-primitive--spacing-b-72{padding-bottom:calc(var(--spacing)*72)}.nv-primitive--spacing-b-80{padding-bottom:calc(var(--spacing)*80)}.nv-primitive--spacing-b-96{padding-bottom:calc(var(--spacing)*96)}.nv-primitive--spacing-b-250{padding-bottom:calc(var(--spacing)*250)}.nv-primitive--spacing-b-px{padding-bottom:1px}.nv-primitive--spacing-b-density-xxs{padding-bottom:var(--spacing-density-xxs)}.nv-primitive--spacing-b-density-xs{padding-bottom:var(--spacing-density-xs)}.nv-primitive--spacing-b-density-sm{padding-bottom:var(--spacing-density-sm)}.nv-primitive--spacing-b-density-md{padding-bottom:var(--spacing-density-md)}.nv-primitive--spacing-b-density-lg{padding-bottom:var(--spacing-density-lg)}.nv-primitive--spacing-b-density-xl{padding-bottom:var(--spacing-density-xl)}.nv-primitive--spacing-b-density-2xl{padding-bottom:var(--spacing-density-2xl)}.nv-primitive--spacing-b-density-3xl{padding-bottom:var(--spacing-density-3xl)}.nv-primitive--spacing-b-density-4xl{padding-bottom:var(--spacing-density-4xl)}.nv-primitive--spacing-b-density-5xl{padding-bottom:var(--spacing-density-5xl)}.nv-primitive--spacing-b-inherit{padding-bottom:inherit}.nv-primitive--spacing-l-0{padding-left:calc(var(--spacing)*0)}.nv-primitive--spacing-l-0_25{padding-left:calc(var(--spacing)*.25)}.nv-primitive--spacing-l-0_5{padding-left:calc(var(--spacing)*.5)}.nv-primitive--spacing-l-0_75{padding-left:calc(var(--spacing)*.75)}.nv-primitive--spacing-l-1{padding-left:calc(var(--spacing)*1)}.nv-primitive--spacing-l-1_5{padding-left:calc(var(--spacing)*1.5)}.nv-primitive--spacing-l-2{padding-left:calc(var(--spacing)*2)}.nv-primitive--spacing-l-2_5{padding-left:calc(var(--spacing)*2.5)}.nv-primitive--spacing-l-3{padding-left:calc(var(--spacing)*3)}.nv-primitive--spacing-l-3_5{padding-left:calc(var(--spacing)*3.5)}.nv-primitive--spacing-l-4{padding-left:calc(var(--spacing)*4)}.nv-primitive--spacing-l-5{padding-left:calc(var(--spacing)*5)}.nv-primitive--spacing-l-6{padding-left:calc(var(--spacing)*6)}.nv-primitive--spacing-l-7{padding-left:calc(var(--spacing)*7)}.nv-primitive--spacing-l-8{padding-left:calc(var(--spacing)*8)}.nv-primitive--spacing-l-9{padding-left:calc(var(--spacing)*9)}.nv-primitive--spacing-l-10{padding-left:calc(var(--spacing)*10)}.nv-primitive--spacing-l-11{padding-left:calc(var(--spacing)*11)}.nv-primitive--spacing-l-12{padding-left:calc(var(--spacing)*12)}.nv-primitive--spacing-l-14{padding-left:calc(var(--spacing)*14)}.nv-primitive--spacing-l-16{padding-left:calc(var(--spacing)*16)}.nv-primitive--spacing-l-18{padding-left:calc(var(--spacing)*18)}.nv-primitive--spacing-l-20{padding-left:calc(var(--spacing)*20)}.nv-primitive--spacing-l-24{padding-left:calc(var(--spacing)*24)}.nv-primitive--spacing-l-28{padding-left:calc(var(--spacing)*28)}.nv-primitive--spacing-l-32{padding-left:calc(var(--spacing)*32)}.nv-primitive--spacing-l-36{padding-left:calc(var(--spacing)*36)}.nv-primitive--spacing-l-40{padding-left:calc(var(--spacing)*40)}.nv-primitive--spacing-l-44{padding-left:calc(var(--spacing)*44)}.nv-primitive--spacing-l-48{padding-left:calc(var(--spacing)*48)}.nv-primitive--spacing-l-52{padding-left:calc(var(--spacing)*52)}.nv-primitive--spacing-l-56{padding-left:calc(var(--spacing)*56)}.nv-primitive--spacing-l-60{padding-left:calc(var(--spacing)*60)}.nv-primitive--spacing-l-64{padding-left:calc(var(--spacing)*64)}.nv-primitive--spacing-l-72{padding-left:calc(var(--spacing)*72)}.nv-primitive--spacing-l-80{padding-left:calc(var(--spacing)*80)}.nv-primitive--spacing-l-96{padding-left:calc(var(--spacing)*96)}.nv-primitive--spacing-l-250{padding-left:calc(var(--spacing)*250)}.nv-primitive--spacing-l-px{padding-left:1px}.nv-primitive--spacing-l-density-xxs{padding-left:var(--spacing-density-xxs)}.nv-primitive--spacing-l-density-xs{padding-left:var(--spacing-density-xs)}.nv-primitive--spacing-l-density-sm{padding-left:var(--spacing-density-sm)}.nv-primitive--spacing-l-density-md{padding-left:var(--spacing-density-md)}.nv-primitive--spacing-l-density-lg{padding-left:var(--spacing-density-lg)}.nv-primitive--spacing-l-density-xl{padding-left:var(--spacing-density-xl)}.nv-primitive--spacing-l-density-2xl{padding-left:var(--spacing-density-2xl)}.nv-primitive--spacing-l-density-3xl{padding-left:var(--spacing-density-3xl)}.nv-primitive--spacing-l-density-4xl{padding-left:var(--spacing-density-4xl)}.nv-primitive--spacing-l-density-5xl{padding-left:var(--spacing-density-5xl)}.nv-primitive--spacing-l-inherit{padding-left:inherit}.nv-primitive--gap-0{gap:calc(var(--spacing)*0)}.nv-primitive--gap-0_25{gap:calc(var(--spacing)*.25)}.nv-primitive--gap-0_5{gap:calc(var(--spacing)*.5)}.nv-primitive--gap-0_75{gap:calc(var(--spacing)*.75)}.nv-primitive--gap-1{gap:calc(var(--spacing)*1)}.nv-primitive--gap-1_5{gap:calc(var(--spacing)*1.5)}.nv-primitive--gap-2{gap:calc(var(--spacing)*2)}.nv-primitive--gap-2_5{gap:calc(var(--spacing)*2.5)}.nv-primitive--gap-3{gap:calc(var(--spacing)*3)}.nv-primitive--gap-3_5{gap:calc(var(--spacing)*3.5)}.nv-primitive--gap-4{gap:calc(var(--spacing)*4)}.nv-primitive--gap-5{gap:calc(var(--spacing)*5)}.nv-primitive--gap-6{gap:calc(var(--spacing)*6)}.nv-primitive--gap-7{gap:calc(var(--spacing)*7)}.nv-primitive--gap-8{gap:calc(var(--spacing)*8)}.nv-primitive--gap-9{gap:calc(var(--spacing)*9)}.nv-primitive--gap-10{gap:calc(var(--spacing)*10)}.nv-primitive--gap-11{gap:calc(var(--spacing)*11)}.nv-primitive--gap-12{gap:calc(var(--spacing)*12)}.nv-primitive--gap-14{gap:calc(var(--spacing)*14)}.nv-primitive--gap-16{gap:calc(var(--spacing)*16)}.nv-primitive--gap-18{gap:calc(var(--spacing)*18)}.nv-primitive--gap-20{gap:calc(var(--spacing)*20)}.nv-primitive--gap-24{gap:calc(var(--spacing)*24)}.nv-primitive--gap-28{gap:calc(var(--spacing)*28)}.nv-primitive--gap-32{gap:calc(var(--spacing)*32)}.nv-primitive--gap-36{gap:calc(var(--spacing)*36)}.nv-primitive--gap-40{gap:calc(var(--spacing)*40)}.nv-primitive--gap-44{gap:calc(var(--spacing)*44)}.nv-primitive--gap-48{gap:calc(var(--spacing)*48)}.nv-primitive--gap-52{gap:calc(var(--spacing)*52)}.nv-primitive--gap-56{gap:calc(var(--spacing)*56)}.nv-primitive--gap-60{gap:calc(var(--spacing)*60)}.nv-primitive--gap-64{gap:calc(var(--spacing)*64)}.nv-primitive--gap-72{gap:calc(var(--spacing)*72)}.nv-primitive--gap-80{gap:calc(var(--spacing)*80)}.nv-primitive--gap-96{gap:calc(var(--spacing)*96)}.nv-primitive--gap-250{gap:calc(var(--spacing)*250)}.nv-primitive--gap-px{gap:1px}.nv-primitive--gap-density-xxs{gap:var(--spacing-density-xxs)}.nv-primitive--gap-density-xs{gap:var(--spacing-density-xs)}.nv-primitive--gap-density-sm{gap:var(--spacing-density-sm)}.nv-primitive--gap-density-md{gap:var(--spacing-density-md)}.nv-primitive--gap-density-lg{gap:var(--spacing-density-lg)}.nv-primitive--gap-density-xl{gap:var(--spacing-density-xl)}.nv-primitive--gap-density-2xl{gap:var(--spacing-density-2xl)}.nv-primitive--gap-density-3xl{gap:var(--spacing-density-3xl)}.nv-primitive--gap-density-4xl{gap:var(--spacing-density-4xl)}.nv-primitive--gap-density-5xl{gap:var(--spacing-density-5xl)}.nv-primitive--gap-inherit{gap:inherit} diff --git a/src/uis/nvcf-ui/ui/vite.config.ts b/src/uis/nvcf-ui/ui/vite.config.ts new file mode 100644 index 000000000..84e5d8110 --- /dev/null +++ b/src/uis/nvcf-ui/ui/vite.config.ts @@ -0,0 +1,78 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import tailwindcss from "@tailwindcss/vite"; +import { devtools } from "@tanstack/devtools-vite"; +import viteReact from "@vitejs/plugin-react"; +import { visualizer } from "rollup-plugin-visualizer"; +import { coverageConfigDefaults, defineConfig } from "vitest/config"; + +// Bazel-only Vitest overrides (set by //ui:test): rules_js's symlinked runfiles +// need fs.strict off to serve the setup file and preserveSymlinks on to dedupe +// the vitest/expect instance. Inert for normal dev/build/`task test`. +const bazelVitest = process.env.BAZEL_VITEST === "1"; + +const config = defineConfig({ + resolve: { + tsconfigPaths: true, + ...(bazelVitest ? { preserveSymlinks: true } : {}), + }, + plugins: [ + devtools(), + tailwindcss(), + viteReact(), + process.env.ANALYZE === "true" && + visualizer({ open: true, filename: "bundle-report.html" }), + ], + server: { + ...(bazelVitest ? { fs: { strict: false } } : {}), + proxy: { + "/api": { + target: "http://localhost:8080", + configure: (proxy) => { + proxy.on("error", () => {}); + }, + }, + }, + }, + build: { + emptyOutDir: true, + }, + test: { + environment: "happy-dom", + globals: true, + setupFiles: ["./vitest.setup.ts"], + coverage: { + provider: "v8", + reporter: ["text", "cobertura"], + exclude: [ + ...coverageConfigDefaults.exclude, + "src/assets/**", + "src/generated/**", + "src/mocks/**", + "src/testing/**", + "src/orval.config.ts", + "src/orval-transformer.ts", + "src/main.tsx", + "src/router.tsx", + "src/rootRoute.tsx", + ], + }, + }, +}); + +export default config; diff --git a/src/uis/nvcf-ui/ui/vitest.setup.ts b/src/uis/nvcf-ui/ui/vitest.setup.ts new file mode 100644 index 000000000..24d70b989 --- /dev/null +++ b/src/uis/nvcf-ui/ui/vitest.setup.ts @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import "@testing-library/jest-dom/vitest"; +import { configure } from "@testing-library/react"; +import { afterAll, afterEach, beforeAll } from "vitest"; +import { server } from "~/mocks/server"; + +// Async queries (findBy/waitFor) default to 1000ms, which loses races against +// parallel transform/import contention in the full suite. Give them headroom. +configure({ asyncUtilTimeout: 5000 }); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); From 0aca7a9abdaf7dc27cb6322cf78aaca19ab27955 Mon Sep 17 00:00:00 2001 From: balaji Date: Tue, 4 Aug 2026 10:32:47 -0700 Subject: [PATCH 2/6] fix(nvcf-ui): classify the nested module and refresh dependency docs Two CI failures from the import. check-nested-modules rejected src/uis/nvcf-ui because a new nested MODULE.bazel is unclassified. Recorded as `ledger`, not `exception`: the category means the migration is outstanding rather than excused, and the entry leaves the file entirely once the subtree builds from the root module. This does re-open the consolidation backlog, which had reached zero services awaiting migration. dependencies.md was stale because the UI's Go backend pulls in github.com/rs/xid. Regenerated with the real generator rather than hand-applying the diff CI printed, since the two have disagreed before. Co-authored-by: Balaji Ganesan --- dependencies.md | 1 + tools/ci/nested-modules.txt | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/dependencies.md b/dependencies.md index 0e455bc41..5f2eaada6 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1231,6 +1231,7 @@ Generated by `go run ./tools/collect-dependencies`. Refresh: `go run ./tools/col - `Go`: `github.com/rivo/uniseg` - `Go`: `github.com/robfig/cron/v3` - `Go`: `github.com/rs/cors` +- `Go`: `github.com/rs/xid` - `Go`: `github.com/rs/zerolog` - `Go`: `github.com/rubenv/sql-migrate` - `Go`: `github.com/ryanuber/go-glob` diff --git a/tools/ci/nested-modules.txt b/tools/ci/nested-modules.txt index e6746d596..8863ad3c4 100644 --- a/tools/ci/nested-modules.txt +++ b/tools/ci/nested-modules.txt @@ -37,6 +37,13 @@ vendored src/compute-plane-services/nvca/vendor/cel.dev/expr scaffolding rules/oci-destinations +# nvcf-ui arrived from ngc/apps/nvcf-ui with its own module already in place +# (aspect_rules_js + rules_nodejs). Its BUILD files use labels relative to that +# module root, so merging into the root module means rewriting every label and +# validating with a Node/pnpm build. Ledger, not exception: the work is +# outstanding, not excused. Tracked in NVIDIA/nvcf#634. +ledger src/uis/nvcf-ui + # No permanent exceptions today. nvsnap was the open question in Phase 1 and it # migrated in NVIDIA/nvcf#471, so it is absent from this file entirely, which is # what a completed migration looks like here. From c33bee30eda19b634846c6df7880f2aa5a9a9399 Mon Sep 17 00:00:00 2001 From: balaji Date: Tue, 4 Aug 2026 13:21:56 -0700 Subject: [PATCH 3/6] feat(nvcf-ui): build the UI from the root Bazel module The import landed source-only: the subtree kept its upstream MODULE.bazel and sat in .bazelignore, so nothing in this repository built, tested or linted its 185 TypeScript and 29 Go files. Excluding the upstream .gitlab-ci.yml had also removed the six quality gates it used to run, replacing them with none. This finishes the migration so the code is actually verified here. check-nested-modules now reports `awaiting migration: 0` again, so the import no longer works against the consolidation. The merge, in the order the failures surfaced: - Lifted aspect_rules_js and rules_nodejs plus the node and npm extensions into the root MODULE.bazel, repointed at //src/uis/nvcf-ui/ui:pnpm-lock.yaml. - Registered the backend in go.work.bazel and rewrote subtree-relative labels. - Dropped the subtree's MODULE.bazel, .bazelrc, rules/ and platforms/; the root already provides distroless_static, rules/oci and every Go dependency the backend imports. - Lifted the subtree's .bazelignore entries into the root. aspect_rules_js refuses to evaluate npm_translate_lock unless every nested node_modules pnpm will create is ignored, so this is required, not tidiness. - Ported go_web_oci_image into the root rules/oci. The root copy was not equivalent; it had no such rule. - Dropped that rule's repo_tag parameter. Its own doc said it existed because package_name() is empty at a repo root; at src/uis/nvcf-ui it is not, so the root's derived load tag applies and the workaround is obsolete. - Removed //platforms:sources and //rules/oci:sources from the container's source bundle. Those are root build infrastructure, not UI source. - Aligned the backend's controller-runtime from v0.24.1 to v0.22.5 to match the rest of the repository. A version-specific replace pins client-go to v0.34.2 while MVS was selecting controller-runtime v0.24.1 from this module, and 0.24 needs client-go APIs that 0.34 does not have. Merges main for the pnpm dependency collector added in #657. Without it the UI's npm tree never reached dependencies.md; the regenerated file now records 531 Node.js packages. Adds the nvcf-ui matrix row so the subtree builds and tests on its own changes. Verified locally: `bazel build //src/uis/nvcf-ui/...` covers 31 targets and `bazel test //src/uis/nvcf-ui/...` passes 5 of 5, including the frontend vitest suite. The controller-runtime downgrade is proven at compile and unit-test level only. The token-watcher is the component using it, and its tests do not exercise cache behaviour, so a reviewer from the UI team should confirm 0.22.5 is acceptable at runtime. Co-authored-by: Balaji Ganesan --- .bazelignore | 16 +- .github/workflows/bazel.yml | 3 +- MODULE.bazel | 17 + MODULE.bazel.lock | 258 +- dependencies.md | 549 +++ go.work.bazel | 1 + rules/oci/defs.bzl | 2 + .../go.bzl => rules/oci/private/go_web.bzl | 9 +- src/uis/nvcf-ui/.bazelignore | 25 - src/uis/nvcf-ui/.bazelrc | 41 - src/uis/nvcf-ui/.bazelversion | 1 - src/uis/nvcf-ui/BUILD.bazel | 15 +- src/uis/nvcf-ui/MODULE.bazel | 102 - src/uis/nvcf-ui/MODULE.bazel.lock | 4081 ----------------- src/uis/nvcf-ui/backend/BUILD.bazel | 18 +- .../backend/cmd/control-plane/BUILD.bazel | 13 +- .../nvcf-ui/backend/cmd/server/BUILD.bazel | 17 +- src/uis/nvcf-ui/backend/go.mod | 3 +- src/uis/nvcf-ui/backend/go.sum | 22 +- .../internal/control-plane/BUILD.bazel | 13 +- .../backend/internal/middleware/BUILD.bazel | 11 +- .../internal/token-watcher/BUILD.bazel | 13 +- .../backend/internal/utils/BUILD.bazel | 11 +- src/uis/nvcf-ui/go.work.bazel | 22 - src/uis/nvcf-ui/platforms/BUILD.bazel | 37 - src/uis/nvcf-ui/rules/oci/BUILD.bazel | 23 - src/uis/nvcf-ui/rules/oci/defs.bzl | 21 - src/uis/nvcf-ui/rules/oci/private/BUILD.bazel | 22 - src/uis/nvcf-ui/rules/oci/private/common.bzl | 130 - src/uis/nvcf-ui/rules/oci/transition.bzl | 42 - src/uis/nvcf-ui/ui/BUILD.bazel | 4 +- tools/ci/nested-modules.txt | 7 - tools/collect-dependencies/go.sum | 1 + 33 files changed, 897 insertions(+), 4653 deletions(-) rename src/uis/nvcf-ui/rules/oci/private/go.bzl => rules/oci/private/go_web.bzl (93%) delete mode 100644 src/uis/nvcf-ui/.bazelignore delete mode 100644 src/uis/nvcf-ui/.bazelrc delete mode 100644 src/uis/nvcf-ui/.bazelversion delete mode 100644 src/uis/nvcf-ui/MODULE.bazel delete mode 100644 src/uis/nvcf-ui/MODULE.bazel.lock delete mode 100644 src/uis/nvcf-ui/go.work.bazel delete mode 100644 src/uis/nvcf-ui/platforms/BUILD.bazel delete mode 100644 src/uis/nvcf-ui/rules/oci/BUILD.bazel delete mode 100644 src/uis/nvcf-ui/rules/oci/defs.bzl delete mode 100644 src/uis/nvcf-ui/rules/oci/private/BUILD.bazel delete mode 100644 src/uis/nvcf-ui/rules/oci/private/common.bzl delete mode 100644 src/uis/nvcf-ui/rules/oci/transition.bzl diff --git a/.bazelignore b/.bazelignore index 32dee88fc..576681741 100644 --- a/.bazelignore +++ b/.bazelignore @@ -45,11 +45,6 @@ src/libraries/python # @stargate_crates from the nested module) do not resolve against the # umbrella root MODULE.bazel. src/libraries/ts -# nvcf-ui is imported with the upstream repo's own MODULE.bazel still in -# place (aspect_rules_js + rules_nodejs). Its BUILD files use labels -# relative to that module root, so the root module keeps it ignored until -# the labels are rewritten and the JS rules are merged into the root. -src/uis/nvcf-ui deploy docs fern @@ -91,6 +86,17 @@ src/libraries/go/lib/pkg/nvkit/build # Bazel-managed caches we keep inside the workspace for CI cache: paths. .bazel-cache +# Lifted from src/uis/nvcf-ui/.bazelignore on migration into the root module. +# ui/node_modules in particular is required: aspect_rules_js refuses to evaluate +# npm_translate_lock unless every nested node_modules pnpm will create is +# ignored here. +src/uis/nvcf-ui/ui/node_modules +src/uis/nvcf-ui/ui/dist +src/uis/nvcf-ui/ui/coverage +src/uis/nvcf-ui/backend/static +src/uis/nvcf-ui/backend/bin +src/uis/nvcf-ui/node_modules + # Lifted from src/compute-plane-services/ess-agent/.bazelignore on migration into the root module. src/compute-plane-services/ess-agent/local src/compute-plane-services/ess-agent/vault diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index ed44dd1ce..4ae3a1919 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -147,7 +147,8 @@ jobs: helm-reval|src/control-plane-services/helm-reval|false|go-root|build-container stargate|src/libraries/rust/stargate|false|go-root|build-container nvsnap|src/compute-plane-services/nvsnap|false|go-root|build-container - image-credential-helper|src/compute-plane-services/image-credential-helper|false|go-root|build-container' + image-credential-helper|src/compute-plane-services/image-credential-helper|false|go-root|build-container + nvcf-ui|src/uis/nvcf-ui|false|go-root|build-container' # Java rows are discovered from component-local bazel-java-ci.json # descriptors. The descriptor's parent directory is the component diff --git a/MODULE.bazel b/MODULE.bazel index 279c2c10e..dfdc35fa2 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -198,6 +198,23 @@ use_repo(go_deps, "cat_dario_mergo", "com_github_armon_go_metrics", "com_github_ # semantics, not a build setting, so the value here is kept at the majority # edition and carries no meaning beyond that. # ============================================================================ +# ---- JS/TS (aspect_rules_js, pnpm-native) ---- +# Lifted from src/uis/nvcf-ui/MODULE.bazel when the UI joined the root module. +# 3.2.x reads the build-script allowlist from pnpm-workspace.yaml (allowBuilds). +bazel_dep(name = "aspect_rules_js", version = "3.2.2") +bazel_dep(name = "rules_nodejs", version = "6.7.3") + +node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") +node.toolchain(node_version = "22.12.0") + +npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm") +npm.npm_translate_lock( + name = "npm", + pnpm_lock = "//src/uis/nvcf-ui/ui:pnpm-lock.yaml", + verify_node_modules_ignored = "//:.bazelignore", +) +use_repo(npm, "npm") + bazel_dep(name = "rules_rust", version = "0.69.0") rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 2de78bc11..0d08dde2e 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -30,9 +30,14 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.2/MODULE.bazel": "276347663a25b0d5bd6cad869252bea3e160c4d980e764b15f3bae7f80b30624", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.2/source.json": "f42051fa42629f0e59b7ac2adf0a55749144b11f1efcd8c697f0ee247181e526", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/MODULE.bazel": "004ba890363d05372a97248c37205ae64b6fa31047629cd2c0895a9d0c7779e8", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "780d1a6522b28f5edb7ea09630748720721dfe27690d65a2d33aa7509de77e07", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", + "https://bcr.bazel.build/modules/aspect_rules_js/3.2.2/MODULE.bazel": "e844196321b64537cfade338f361633e7d5fe084eee3214fc531b9e7b9838813", + "https://bcr.bazel.build/modules/aspect_rules_js/3.2.2/source.json": "0e650e493b3bc784dc3f8240ff00b4b32258cde6d021ef6b1db4e387bcfd6a9a", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/source.json": "605086bbc197743a0d360f7ddc550a1d4dfa0441bc807236e17170f636153348", "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", @@ -51,14 +56,19 @@ "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.32.0/MODULE.bazel": "095d67022a58cb20f7e20e1aefecfa65257a222c18a938e2914fd257b5f1ccdc", "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e", "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", + "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", - "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", + "https://bcr.bazel.build/modules/bazel_lib/3.2.2/MODULE.bazel": "e2c890c8a515d6bca9c66d47718aa9e44b458fde64ec7204b8030bf2d349058c", + "https://bcr.bazel.build/modules/bazel_lib/3.2.2/source.json": "9e84e115c20e14652c5c21401ae85ff4daa8702e265b5c0b3bf89353f17aa212", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -85,7 +95,8 @@ "https://bcr.bazel.build/modules/contrib_rules_jvm/0.33.0/MODULE.bazel": "1aba514585748372ee392484b2645f0bbcb13ad4271d5b7cace018280f15bae1", "https://bcr.bazel.build/modules/contrib_rules_jvm/0.33.0/source.json": "e52d62ce2a2b8a513d6afe9178acc06a4349c9b0b4c0934d59085ed9df4b4de0", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", @@ -105,7 +116,8 @@ "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.1.0/MODULE.bazel": "b512c9698968bd4548eecec8fb9bab4327c6f50fb0d12cc56e9a4d32a9a78458", "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.1.0/source.json": "25ec6993f3acde436250883f3ab7945ff90a2fe42407af2c66f65f0f3e5b2959", "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", - "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", + "https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2", + "https://bcr.bazel.build/modules/jq.bzl/0.4.0/source.json": "52ec7530c4618e03f634b30ff719814a68d7d39c235938b7aa2abbfe1eb1c52c", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", @@ -178,6 +190,7 @@ "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", + "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", @@ -242,6 +255,8 @@ "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09", "https://bcr.bazel.build/modules/rules_oci/2.2.7/MODULE.bazel": "f6150e4b224d459f7f6523ef65967464ca4efdd266c7fbf2f5a2a51011957e0c", "https://bcr.bazel.build/modules/rules_oci/2.2.7/source.json": "b099f02af330f47f19dc67fc9300ef6e1937a8c86882690db0e7a2fcea8c7f6b", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", @@ -308,9 +323,10 @@ "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/source.json": "20143442376c03426f6135292ba02d825cb75308aa47e6bf42dd4cc5a435c2ff", "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", - "https://bcr.bazel.build/modules/tar.bzl/0.5.1/source.json": "deed3094f7cc779ed1d37a68403847b0e38d9dd9d931e03cb90825f3368b515f", "https://bcr.bazel.build/modules/toml.bzl/0.3.0/MODULE.bazel": "5016e5dd1ad2200e119a4b28b2b3935e276c4b480f2fe3e952bea7eeba88f578", "https://bcr.bazel.build/modules/toml.bzl/0.3.0/source.json": "0cf7c878c419b37ddb55f3dd93dd7c0c409bd7c4efacb3da504e0748780b2fa9", "https://bcr.bazel.build/modules/toolchains_protoc/0.5.0/MODULE.bazel": "e649dcd74790d8b186517588c827a777dfa67acfc4cbd733721c4be143ea107f", @@ -319,7 +335,8 @@ "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", - "https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3", + "https://bcr.bazel.build/modules/yq.bzl/0.3.4/MODULE.bazel": "d3a270662f5d766cd7229732d65a5a5bc485240c3007343dd279edfb60c9ae27", + "https://bcr.bazel.build/modules/yq.bzl/0.3.4/source.json": "786dafdc2843722da3416e4343ee1a05237227f068590779a6e8496a2064c0f9", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", @@ -343,6 +360,27 @@ } } }, + "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { + "general": { + "bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=", + "usagesDigest": "qAHU62OXACzqqTdD33AhM8HizjWUgO22B+s6YCegb5I=", + "recordedInputs": [ + "REPO_MAPPING:aspect_tools_telemetry+,bazel_lib bazel_lib+", + "REPO_MAPPING:aspect_tools_telemetry+,bazel_skylib bazel_skylib+" + ], + "generatedRepoSpecs": { + "aspect_tools_telemetry_report": { + "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", + "attributes": { + "deps": { + "aspect_rules_js": "3.2.2", + "aspect_tools_telemetry": "0.3.3" + } + } + } + } + } + }, "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { "general": { "bzlTransitiveDigest": "b+RP7Sgl8KN0VHamrgTqzGLuYPcQ/Mo4ptNkkHUIIlA=", @@ -414,9 +452,140 @@ } } }, + "@@rules_nodejs+//nodejs:extensions.bzl%node": { + "general": { + "bzlTransitiveDigest": "4pUxCNc22K4I+6+4Nxu52Hur12tFRfa1JMsN5mdDv60=", + "usagesDigest": "VYNEKki4Z9HleRZXtIw3+YqQgBC8q1SRZq29ELRCaDk=", + "recordedInputs": [], + "generatedRepoSpecs": { + "nodejs_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "nodejs_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "nodejs_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "nodejs_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "nodejs_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "nodejs_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "nodejs_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "nodejs_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "nodejs": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "nodejs" + } + } + } + } + }, "@@rules_oci+//oci:extensions.bzl%oci": { "general": { - "bzlTransitiveDigest": "IPpbSHX7+8yLBfvZyia9qXU/WRxkP+dWAO8m5+BsBY8=", + "bzlTransitiveDigest": "Vjs2aLDiztq2JcqxCwBcmhWsa/nb80kDJ1sMT56l+0k=", "usagesDigest": "EDzShmIAPaiLhkdqPiXbibm5GmGgklN1Yz6iboWtAQ4=", "recordedInputs": [ "REPO_MAPPING:aspect_bazel_lib+,bazel_tools bazel_tools", @@ -20237,52 +20406,79 @@ } } }, - "@@tar.bzl+//tar:extensions.bzl%toolchains": { + "@@yq.bzl+//yq:extensions.bzl%yq": { "general": { - "bzlTransitiveDigest": "/2afh6fPjq/rcyE/jztQDK3ierehmFFngfvmqyRv72M=", - "usagesDigest": "maF8qsAIqeH1ey8pxP0gNZbvJt34kLZvTFeQ0ntrJVA=", + "bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=", + "usagesDigest": "z7g7IZt6aaEeYztkX2Hs5qlSifRJcDTGz9K/CD4yOFI=", "recordedInputs": [], "generatedRepoSpecs": { - "bsd_tar_toolchains": { - "repoRuleId": "@@tar.bzl+//tar/toolchain:toolchain.bzl%tar_toolchains_repo", + "yq_darwin_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", "attributes": { - "user_repository_name": "bsd_tar_toolchains" + "platform": "darwin_amd64", + "version": "4.45.2" } }, - "bsd_tar_toolchains_darwin_amd64": { - "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "yq_darwin_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", "attributes": { - "platform": "darwin_amd64" + "platform": "darwin_arm64", + "version": "4.45.2" } }, - "bsd_tar_toolchains_darwin_arm64": { - "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "yq_linux_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", "attributes": { - "platform": "darwin_arm64" + "platform": "linux_amd64", + "version": "4.45.2" } }, - "bsd_tar_toolchains_linux_amd64": { - "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "yq_linux_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", "attributes": { - "platform": "linux_amd64" + "platform": "linux_arm64", + "version": "4.45.2" } }, - "bsd_tar_toolchains_linux_arm64": { - "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "yq_linux_s390x": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", "attributes": { - "platform": "linux_arm64" + "platform": "linux_s390x", + "version": "4.45.2" } }, - "bsd_tar_toolchains_windows_amd64": { - "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "yq_linux_riscv64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", "attributes": { - "platform": "windows_amd64" + "platform": "linux_riscv64", + "version": "4.45.2" } }, - "bsd_tar_toolchains_windows_arm64": { - "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "yq_linux_ppc64le": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", "attributes": { - "platform": "windows_arm64" + "platform": "linux_ppc64le", + "version": "4.45.2" + } + }, + "yq_windows_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "windows_amd64", + "version": "4.45.2" + } + }, + "yq_windows_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "windows_arm64", + "version": "4.45.2" + } + }, + "yq_toolchains": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:toolchain.bzl%yq_toolchains_repo", + "attributes": { + "user_repository_name": "yq" } } } diff --git a/dependencies.md b/dependencies.md index e07994684..dd78fca7a 100644 --- a/dependencies.md +++ b/dependencies.md @@ -4,6 +4,14 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Rust`: `moka 0.12` +## (MIT OR CC0-1.0) + +- `Node.js`: `type-fest@5.6.0` + +## 0BSD + +- `Node.js`: `tslib@2.8.1` + ## Apache-2.0 - `Go`: `cel.dev/expr` @@ -804,6 +812,15 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Java`: `tools.jackson.core:jackson-core:3.1.4` (Jackson-core; https://github.com/FasterXML/jackson-core) - `Java`: `tools.jackson.core:jackson-databind:3.1.4` (jackson-databind; https://github.com/FasterXML/jackson) - `Java`: `tools.jackson.module:jackson-module-blackbird:3.1.4` (Jackson module: Blackbird; https://github.com/FasterXML/jackson-modules-base) +- `Node.js`: `@nvidia/foundations-react-core@1.7.0` +- `Node.js`: `@nvidia/foundations-tailwind-plugin@1.7.0` +- `Node.js`: `aria-query@5.3.0` +- `Node.js`: `class-variance-authority@0.7.1` +- `Node.js`: `detect-libc@2.1.2` +- `Node.js`: `expect-type@1.3.0` +- `Node.js`: `human-signals@8.0.1` +- `Node.js`: `typedoc@0.28.19` +- `Node.js`: `typescript@5.9.3` - `Python`: `gradio==6.7.0` - `Python`: `grpcio==1.59.3` - `Python`: `grpcio-health-checking==1.59.3` @@ -884,6 +901,10 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Rust`: `uom 0.36` - `Rust`: `uuid` (`uuid 1`, `uuid 1.16.0`, `uuid =1.22.0`) +## BlueOak-1.0.0 + +- `Node.js`: `minimatch@10.2.5` + ## Bouncy Castle Licence - `Java`: `org.bouncycastle:bcpkix-jdk18on:1.80` (Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs; https://www.bouncycastle.org/download/bouncy-castle-java/) @@ -895,6 +916,18 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Python`: `jinja2>=3.0.3` +## BSD-2-Clause + +- `Node.js`: `css-select@5.2.2` +- `Node.js`: `css-what@6.2.2` +- `Node.js`: `domelementtype@2.3.0` +- `Node.js`: `domhandler@5.0.3` +- `Node.js`: `domutils@3.2.2` +- `Node.js`: `entities@4.5.0` +- `Node.js`: `entities@7.0.1` +- `Node.js`: `esutils@2.0.3` +- `Node.js`: `nth-check@2.1.1` + ## BSD-2-Clause / CC0-1.0 - `Java`: `org.hdrhistogram:HdrHistogram:2.2.2` (HdrHistogram; http://hdrhistogram.github.io/HdrHistogram/) @@ -994,6 +1027,13 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Java`: `org.ow2.asm:asm-tree:9.9` (asm-tree; http://asm.ow2.io/) - `Java`: `org.ow2.asm:asm-util:9.9` (asm-util; http://asm.ow2.io/) - `Java`: `org.ow2.asm:asm:9.9` (asm; http://asm.ow2.io/) +- `Node.js`: `fast-uri@3.1.0` +- `Node.js`: `istanbul-lib-coverage@3.2.2` +- `Node.js`: `istanbul-lib-report@3.0.1` +- `Node.js`: `istanbul-reports@3.2.0` +- `Node.js`: `source-map-js@1.2.1` +- `Node.js`: `source-map@0.7.6` +- `Node.js`: `tough-cookie@6.0.1` - `Python`: `httpx==0.24.1` - `Python`: `uvicorn==0.29.0` @@ -1042,6 +1082,27 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Go`: `github.com/davecgh/go-spew` - `Go`: `github.com/decred/dcrd/dcrec/secp256k1/v4` +- `Node.js`: `@ungap/structured-clone@1.3.0` +- `Node.js`: `boolbase@1.0.0` +- `Node.js`: `cli-width@4.1.0` +- `Node.js`: `cliui@8.0.1` +- `Node.js`: `cliui@9.0.1` +- `Node.js`: `fastq@1.20.1` +- `Node.js`: `get-caller-file@2.0.5` +- `Node.js`: `glob-parent@5.1.2` +- `Node.js`: `graceful-fs@4.2.11` +- `Node.js`: `isexe@2.0.0` +- `Node.js`: `lucide-react@1.3.0` +- `Node.js`: `mute-stream@3.0.0` +- `Node.js`: `picocolors@1.1.1` +- `Node.js`: `semver@7.7.4` +- `Node.js`: `siginfo@2.0.0` +- `Node.js`: `signal-exit@4.1.0` +- `Node.js`: `which@2.0.2` +- `Node.js`: `y18n@5.0.8` +- `Node.js`: `yaml@2.8.3` +- `Node.js`: `yargs-parser@21.1.1` +- `Node.js`: `yargs-parser@22.0.0` ## MIT @@ -1300,6 +1361,465 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Java`: `org.projectlombok:lombok:1.18.46` (Project Lombok; https://projectlombok.org) - `Java`: `org.slf4j:jul-to-slf4j:2.0.18` (JUL to SLF4J bridge; http://www.slf4j.org) - `Java`: `org.slf4j:slf4j-api:2.0.18` (SLF4J API Module; http://www.slf4j.org) +- `Node.js`: `@adobe/css-tools@4.4.4` +- `Node.js`: `@babel/code-frame@7.29.0` +- `Node.js`: `@babel/helper-string-parser@7.27.1` +- `Node.js`: `@babel/helper-validator-identifier@7.28.5` +- `Node.js`: `@babel/parser@7.29.2` +- `Node.js`: `@babel/runtime@7.29.2` +- `Node.js`: `@babel/types@7.29.0` +- `Node.js`: `@bcoe/v8-coverage@1.0.2` +- `Node.js`: `@commander-js/extra-typings@14.0.0` +- `Node.js`: `@date-fns/tz@1.4.1` +- `Node.js`: `@emnapi/core@1.10.0` +- `Node.js`: `@emnapi/runtime@1.10.0` +- `Node.js`: `@emnapi/wasi-threads@1.2.1` +- `Node.js`: `@esbuild/aix-ppc64@0.27.7` +- `Node.js`: `@esbuild/android-arm64@0.27.7` +- `Node.js`: `@esbuild/android-arm@0.27.7` +- `Node.js`: `@esbuild/android-x64@0.27.7` +- `Node.js`: `@esbuild/darwin-arm64@0.27.7` +- `Node.js`: `@esbuild/darwin-x64@0.27.7` +- `Node.js`: `@esbuild/freebsd-arm64@0.27.7` +- `Node.js`: `@esbuild/freebsd-x64@0.27.7` +- `Node.js`: `@esbuild/linux-arm64@0.27.7` +- `Node.js`: `@esbuild/linux-arm@0.27.7` +- `Node.js`: `@esbuild/linux-ia32@0.27.7` +- `Node.js`: `@esbuild/linux-loong64@0.27.7` +- `Node.js`: `@esbuild/linux-mips64el@0.27.7` +- `Node.js`: `@esbuild/linux-ppc64@0.27.7` +- `Node.js`: `@esbuild/linux-riscv64@0.27.7` +- `Node.js`: `@esbuild/linux-s390x@0.27.7` +- `Node.js`: `@esbuild/linux-x64@0.27.7` +- `Node.js`: `@esbuild/netbsd-arm64@0.27.7` +- `Node.js`: `@esbuild/netbsd-x64@0.27.7` +- `Node.js`: `@esbuild/openbsd-arm64@0.27.7` +- `Node.js`: `@esbuild/openbsd-x64@0.27.7` +- `Node.js`: `@esbuild/openharmony-arm64@0.27.7` +- `Node.js`: `@esbuild/sunos-x64@0.27.7` +- `Node.js`: `@esbuild/win32-arm64@0.27.7` +- `Node.js`: `@esbuild/win32-ia32@0.27.7` +- `Node.js`: `@esbuild/win32-x64@0.27.7` +- `Node.js`: `@faker-js/faker@10.4.0` +- `Node.js`: `@gerrit0/mini-shiki@3.23.0` +- `Node.js`: `@inquirer/ansi@2.0.5` +- `Node.js`: `@inquirer/confirm@6.0.12` +- `Node.js`: `@inquirer/core@11.1.9` +- `Node.js`: `@inquirer/figures@2.0.5` +- `Node.js`: `@inquirer/type@4.0.5` +- `Node.js`: `@jridgewell/gen-mapping@0.3.13` +- `Node.js`: `@jridgewell/remapping@2.3.5` +- `Node.js`: `@jridgewell/resolve-uri@3.1.2` +- `Node.js`: `@jridgewell/sourcemap-codec@1.5.5` +- `Node.js`: `@jridgewell/trace-mapping@0.3.31` +- `Node.js`: `@mswjs/interceptors@0.41.6` +- `Node.js`: `@napi-rs/wasm-runtime@1.1.4` +- `Node.js`: `@nodelib/fs.scandir@2.1.5` +- `Node.js`: `@nodelib/fs.stat@2.0.5` +- `Node.js`: `@nodelib/fs.walk@1.2.8` +- `Node.js`: `@open-draft/deferred-promise@2.2.0` +- `Node.js`: `@open-draft/deferred-promise@3.0.0` +- `Node.js`: `@open-draft/logger@0.3.0` +- `Node.js`: `@open-draft/until@2.1.0` +- `Node.js`: `@orval/angular@8.9.0` +- `Node.js`: `@orval/axios@8.9.0` +- `Node.js`: `@orval/core@8.9.0` +- `Node.js`: `@orval/fetch@8.9.0` +- `Node.js`: `@orval/hono@8.9.0` +- `Node.js`: `@orval/mcp@8.9.0` +- `Node.js`: `@orval/mock@8.9.0` +- `Node.js`: `@orval/query@8.9.0` +- `Node.js`: `@orval/solid-start@8.9.0` +- `Node.js`: `@orval/swr@8.9.0` +- `Node.js`: `@orval/zod@8.9.0` +- `Node.js`: `@oxc-parser/binding-android-arm-eabi@0.120.0` +- `Node.js`: `@oxc-parser/binding-android-arm64@0.120.0` +- `Node.js`: `@oxc-parser/binding-darwin-arm64@0.120.0` +- `Node.js`: `@oxc-parser/binding-darwin-x64@0.120.0` +- `Node.js`: `@oxc-parser/binding-freebsd-x64@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-arm-gnueabihf@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-arm-musleabihf@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-arm64-gnu@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-arm64-musl@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-ppc64-gnu@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-riscv64-gnu@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-riscv64-musl@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-s390x-gnu@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-x64-gnu@0.120.0` +- `Node.js`: `@oxc-parser/binding-linux-x64-musl@0.120.0` +- `Node.js`: `@oxc-parser/binding-openharmony-arm64@0.120.0` +- `Node.js`: `@oxc-parser/binding-wasm32-wasi@0.120.0` +- `Node.js`: `@oxc-parser/binding-win32-arm64-msvc@0.120.0` +- `Node.js`: `@oxc-parser/binding-win32-ia32-msvc@0.120.0` +- `Node.js`: `@oxc-parser/binding-win32-x64-msvc@0.120.0` +- `Node.js`: `@oxc-project/types@0.120.0` +- `Node.js`: `@oxc-project/types@0.127.0` +- `Node.js`: `@radix-ui/number@1.1.1` +- `Node.js`: `@radix-ui/primitive@1.1.3` +- `Node.js`: `@radix-ui/react-collection@1.1.7` +- `Node.js`: `@radix-ui/react-compose-refs@1.1.2` +- `Node.js`: `@radix-ui/react-context@1.1.2` +- `Node.js`: `@radix-ui/react-context@1.1.3` +- `Node.js`: `@radix-ui/react-direction@1.1.1` +- `Node.js`: `@radix-ui/react-label@2.1.8` +- `Node.js`: `@radix-ui/react-primitive@2.1.3` +- `Node.js`: `@radix-ui/react-primitive@2.1.4` +- `Node.js`: `@radix-ui/react-progress@1.1.8` +- `Node.js`: `@radix-ui/react-slider@1.3.6` +- `Node.js`: `@radix-ui/react-slot@1.2.3` +- `Node.js`: `@radix-ui/react-slot@1.2.4` +- `Node.js`: `@radix-ui/react-use-controllable-state@1.2.2` +- `Node.js`: `@radix-ui/react-use-effect-event@0.0.2` +- `Node.js`: `@radix-ui/react-use-layout-effect@1.1.1` +- `Node.js`: `@radix-ui/react-use-previous@1.1.1` +- `Node.js`: `@radix-ui/react-use-size@1.1.1` +- `Node.js`: `@rolldown/binding-android-arm64@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-darwin-arm64@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-darwin-x64@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-freebsd-x64@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-linux-arm64-musl@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-linux-x64-gnu@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-linux-x64-musl@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-openharmony-arm64@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-wasm32-wasi@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17` +- `Node.js`: `@rolldown/binding-win32-x64-msvc@1.0.0-rc.17` +- `Node.js`: `@rolldown/pluginutils@1.0.0-rc.17` +- `Node.js`: `@rolldown/pluginutils@1.0.0-rc.7` +- `Node.js`: `@scalar/helpers@0.5.2` +- `Node.js`: `@scalar/json-magic@0.12.8` +- `Node.js`: `@scalar/openapi-parser@0.25.12` +- `Node.js`: `@scalar/openapi-types@0.8.0` +- `Node.js`: `@scalar/openapi-upgrader@0.2.6` +- `Node.js`: `@sec-ant/readable-stream@0.4.1` +- `Node.js`: `@shikijs/core@4.1.0` +- `Node.js`: `@shikijs/engine-javascript@4.1.0` +- `Node.js`: `@shikijs/engine-oniguruma@3.23.0` +- `Node.js`: `@shikijs/langs@3.23.0` +- `Node.js`: `@shikijs/langs@4.1.0` +- `Node.js`: `@shikijs/primitive@4.1.0` +- `Node.js`: `@shikijs/themes@3.23.0` +- `Node.js`: `@shikijs/themes@4.1.0` +- `Node.js`: `@shikijs/types@3.23.0` +- `Node.js`: `@shikijs/types@4.1.0` +- `Node.js`: `@shikijs/vscode-textmate@10.0.2` +- `Node.js`: `@sindresorhus/merge-streams@4.0.0` +- `Node.js`: `@solid-primitives/event-listener@2.4.5` +- `Node.js`: `@solid-primitives/keyboard@1.3.5` +- `Node.js`: `@solid-primitives/resize-observer@2.1.5` +- `Node.js`: `@solid-primitives/rootless@1.5.3` +- `Node.js`: `@solid-primitives/static-store@0.1.3` +- `Node.js`: `@solid-primitives/utils@6.4.0` +- `Node.js`: `@standard-schema/spec@1.1.0` +- `Node.js`: `@tabby_ai/hijri-converter@1.0.5` +- `Node.js`: `@tailwindcss/node@4.2.4` +- `Node.js`: `@tailwindcss/oxide-android-arm64@4.2.4` +- `Node.js`: `@tailwindcss/oxide-darwin-arm64@4.2.4` +- `Node.js`: `@tailwindcss/oxide-darwin-x64@4.2.4` +- `Node.js`: `@tailwindcss/oxide-freebsd-x64@4.2.4` +- `Node.js`: `@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4` +- `Node.js`: `@tailwindcss/oxide-linux-arm64-gnu@4.2.4` +- `Node.js`: `@tailwindcss/oxide-linux-arm64-musl@4.2.4` +- `Node.js`: `@tailwindcss/oxide-linux-x64-gnu@4.2.4` +- `Node.js`: `@tailwindcss/oxide-linux-x64-musl@4.2.4` +- `Node.js`: `@tailwindcss/oxide-wasm32-wasi@4.2.4` +- `Node.js`: `@tailwindcss/oxide-win32-arm64-msvc@4.2.4` +- `Node.js`: `@tailwindcss/oxide-win32-x64-msvc@4.2.4` +- `Node.js`: `@tailwindcss/oxide@4.2.4` +- `Node.js`: `@tailwindcss/typography@0.5.19` +- `Node.js`: `@tailwindcss/vite@4.2.4` +- `Node.js`: `@tanstack/devtools-client@0.0.8` +- `Node.js`: `@tanstack/devtools-event-bus@0.4.2` +- `Node.js`: `@tanstack/devtools-event-client@0.5.0` +- `Node.js`: `@tanstack/devtools-ui@0.6.0` +- `Node.js`: `@tanstack/devtools-vite@0.8.1` +- `Node.js`: `@tanstack/devtools@0.12.5` +- `Node.js`: `@tanstack/history@1.162.0` +- `Node.js`: `@tanstack/query-core@5.100.1` +- `Node.js`: `@tanstack/query-devtools@5.100.0` +- `Node.js`: `@tanstack/react-devtools@0.10.8` +- `Node.js`: `@tanstack/react-query-devtools@5.100.0` +- `Node.js`: `@tanstack/react-query@5.100.1` +- `Node.js`: `@tanstack/react-router-devtools@1.167.0` +- `Node.js`: `@tanstack/react-router@1.170.18` +- `Node.js`: `@tanstack/react-store@0.9.3` +- `Node.js`: `@tanstack/react-table@8.21.3` +- `Node.js`: `@tanstack/router-core@1.171.15` +- `Node.js`: `@tanstack/router-devtools-core@1.168.0` +- `Node.js`: `@tanstack/store@0.9.3` +- `Node.js`: `@tanstack/table-core@8.21.3` +- `Node.js`: `@testing-library/dom@10.4.1` +- `Node.js`: `@testing-library/jest-dom@6.9.1` +- `Node.js`: `@testing-library/react@16.3.2` +- `Node.js`: `@testing-library/user-event@14.6.1` +- `Node.js`: `@tybys/wasm-util@0.10.1` +- `Node.js`: `@types/aria-query@5.0.4` +- `Node.js`: `@types/chai@5.2.3` +- `Node.js`: `@types/deep-eql@4.0.2` +- `Node.js`: `@types/estree@1.0.8` +- `Node.js`: `@types/hast@3.0.4` +- `Node.js`: `@types/lodash-es@4.17.12` +- `Node.js`: `@types/lodash@4.17.24` +- `Node.js`: `@types/mdast@4.0.4` +- `Node.js`: `@types/node@22.19.17` +- `Node.js`: `@types/react-dom@19.2.3` +- `Node.js`: `@types/react@19.2.14` +- `Node.js`: `@types/set-cookie-parser@2.4.10` +- `Node.js`: `@types/statuses@2.0.6` +- `Node.js`: `@types/unist@3.0.3` +- `Node.js`: `@types/whatwg-mimetype@3.0.2` +- `Node.js`: `@types/ws@8.18.1` +- `Node.js`: `@vitejs/plugin-react@6.0.1` +- `Node.js`: `@vitest/coverage-v8@4.1.5` +- `Node.js`: `@vitest/expect@4.1.5` +- `Node.js`: `@vitest/mocker@4.1.5` +- `Node.js`: `@vitest/pretty-format@4.1.5` +- `Node.js`: `@vitest/runner@4.1.5` +- `Node.js`: `@vitest/snapshot@4.1.5` +- `Node.js`: `@vitest/spy@4.1.5` +- `Node.js`: `@vitest/utils@4.1.5` +- `Node.js`: `acorn@8.16.0` +- `Node.js`: `ajv-draft-04@1.0.0` +- `Node.js`: `ajv-formats@3.0.1` +- `Node.js`: `ajv@8.20.0` +- `Node.js`: `ansi-colors@4.1.3` +- `Node.js`: `ansi-regex@5.0.1` +- `Node.js`: `ansi-regex@6.2.2` +- `Node.js`: `ansi-styles@4.3.0` +- `Node.js`: `ansi-styles@5.2.0` +- `Node.js`: `ansi-styles@6.2.3` +- `Node.js`: `assertion-error@2.0.1` +- `Node.js`: `ast-v8-to-istanbul@1.0.0` +- `Node.js`: `balanced-match@4.0.4` +- `Node.js`: `brace-expansion@5.0.5` +- `Node.js`: `braces@3.0.3` +- `Node.js`: `bundle-name@4.1.0` +- `Node.js`: `ccount@2.0.1` +- `Node.js`: `chai@6.2.2` +- `Node.js`: `chalk@5.6.2` +- `Node.js`: `character-entities-html4@2.1.0` +- `Node.js`: `character-entities-legacy@3.0.0` +- `Node.js`: `chokidar@5.0.0` +- `Node.js`: `clsx@2.1.1` +- `Node.js`: `color-convert@2.0.1` +- `Node.js`: `color-name@1.1.4` +- `Node.js`: `comma-separated-tokens@2.0.3` +- `Node.js`: `commander@14.0.3` +- `Node.js`: `compare-versions@6.1.1` +- `Node.js`: `convert-source-map@2.0.0` +- `Node.js`: `cookie-es@3.1.1` +- `Node.js`: `cookie@1.1.1` +- `Node.js`: `cross-spawn@7.0.6` +- `Node.js`: `css.escape@1.5.1` +- `Node.js`: `cssesc@3.0.0` +- `Node.js`: `csstype@3.2.3` +- `Node.js`: `date-fns-jalali@4.1.0-0` +- `Node.js`: `date-fns@4.1.0` +- `Node.js`: `dayjs@1.11.21` +- `Node.js`: `debug@4.4.3` +- `Node.js`: `default-browser-id@5.0.1` +- `Node.js`: `default-browser@5.5.0` +- `Node.js`: `define-lazy-prop@3.0.0` +- `Node.js`: `dequal@2.0.3` +- `Node.js`: `devlop@1.1.0` +- `Node.js`: `dom-accessibility-api@0.5.16` +- `Node.js`: `dom-accessibility-api@0.6.3` +- `Node.js`: `dom-serializer@2.0.0` +- `Node.js`: `emoji-regex@10.6.0` +- `Node.js`: `emoji-regex@8.0.0` +- `Node.js`: `enhanced-resolve@5.20.1` +- `Node.js`: `enquirer@2.4.1` +- `Node.js`: `es-module-lexer@2.1.0` +- `Node.js`: `esbuild@0.27.7` +- `Node.js`: `escalade@3.2.0` +- `Node.js`: `estree-walker@3.0.3` +- `Node.js`: `execa@9.6.1` +- `Node.js`: `fast-deep-equal@3.1.3` +- `Node.js`: `fast-glob@3.3.3` +- `Node.js`: `fast-string-truncated-width@3.0.3` +- `Node.js`: `fast-string-width@3.0.2` +- `Node.js`: `fast-wrap-ansi@0.2.0` +- `Node.js`: `fdir@6.5.0` +- `Node.js`: `figures@6.1.0` +- `Node.js`: `fill-range@7.1.1` +- `Node.js`: `find-up@8.0.0` +- `Node.js`: `fs-extra@11.3.4` +- `Node.js`: `fsevents@2.3.3` +- `Node.js`: `get-east-asian-width@1.5.0` +- `Node.js`: `get-stream@9.0.1` +- `Node.js`: `globby@16.1.0` +- `Node.js`: `goober@2.1.18` +- `Node.js`: `graphql@16.13.2` +- `Node.js`: `happy-dom@20.9.0` +- `Node.js`: `has-flag@4.0.0` +- `Node.js`: `hast-util-to-html@9.0.5` +- `Node.js`: `hast-util-whitespace@3.0.0` +- `Node.js`: `he@1.2.0` +- `Node.js`: `headers-polyfill@5.0.1` +- `Node.js`: `html-escaper@2.0.2` +- `Node.js`: `html-void-elements@3.0.0` +- `Node.js`: `ignore@7.0.5` +- `Node.js`: `indent-string@4.0.0` +- `Node.js`: `is-docker@3.0.0` +- `Node.js`: `is-extglob@2.1.1` +- `Node.js`: `is-fullwidth-code-point@3.0.0` +- `Node.js`: `is-glob@4.0.3` +- `Node.js`: `is-in-ssh@1.0.0` +- `Node.js`: `is-inside-container@1.0.0` +- `Node.js`: `is-node-process@1.2.0` +- `Node.js`: `is-number@7.0.0` +- `Node.js`: `is-path-inside@4.0.0` +- `Node.js`: `is-plain-obj@4.1.0` +- `Node.js`: `is-stream@4.0.1` +- `Node.js`: `is-unicode-supported@2.1.0` +- `Node.js`: `is-wsl@3.1.1` +- `Node.js`: `jiti@2.6.1` +- `Node.js`: `js-tokens@10.0.0` +- `Node.js`: `js-tokens@4.0.0` +- `Node.js`: `js-yaml@4.1.1` +- `Node.js`: `json-schema-traverse@1.0.0` +- `Node.js`: `jsonfile@6.2.1` +- `Node.js`: `jsonpointer@5.0.1` +- `Node.js`: `launch-editor@2.13.2` +- `Node.js`: `leven@4.1.0` +- `Node.js`: `linkify-it@5.0.0` +- `Node.js`: `locate-path@8.0.0` +- `Node.js`: `lodash-es@4.18.1` +- `Node.js`: `lunr@2.3.9` +- `Node.js`: `lz-string@1.5.0` +- `Node.js`: `magic-string@0.30.21` +- `Node.js`: `magicast@0.5.2` +- `Node.js`: `make-dir@4.0.0` +- `Node.js`: `markdown-it@14.1.1` +- `Node.js`: `mdast-util-to-hast@13.2.1` +- `Node.js`: `mdurl@2.0.0` +- `Node.js`: `merge2@1.4.1` +- `Node.js`: `micromark-util-character@2.1.1` +- `Node.js`: `micromark-util-encode@2.0.1` +- `Node.js`: `micromark-util-sanitize-uri@2.0.1` +- `Node.js`: `micromark-util-symbol@2.0.1` +- `Node.js`: `micromark-util-types@2.0.2` +- `Node.js`: `micromatch@4.0.8` +- `Node.js`: `min-indent@1.0.1` +- `Node.js`: `ms@2.1.3` +- `Node.js`: `msw@2.13.6` +- `Node.js`: `nanoid@3.3.11` +- `Node.js`: `node-html-parser@7.1.0` +- `Node.js`: `npm-run-path@6.0.0` +- `Node.js`: `obug@2.1.1` +- `Node.js`: `oniguruma-parser@0.12.2` +- `Node.js`: `oniguruma-to-es@4.3.6` +- `Node.js`: `open@11.0.0` +- `Node.js`: `orval@8.9.0` +- `Node.js`: `outvariant@1.4.3` +- `Node.js`: `oxc-parser@0.120.0` +- `Node.js`: `p-limit@4.0.0` +- `Node.js`: `p-locate@6.0.0` +- `Node.js`: `parse-ms@4.0.0` +- `Node.js`: `path-key@3.1.1` +- `Node.js`: `path-key@4.0.0` +- `Node.js`: `path-to-regexp@6.3.0` +- `Node.js`: `pathe@2.0.3` +- `Node.js`: `picomatch@2.3.2` +- `Node.js`: `picomatch@4.0.4` +- `Node.js`: `postcss-selector-parser@6.0.10` +- `Node.js`: `postcss@8.5.10` +- `Node.js`: `powershell-utils@0.1.0` +- `Node.js`: `pretty-format@27.5.1` +- `Node.js`: `pretty-ms@9.3.0` +- `Node.js`: `property-information@7.1.0` +- `Node.js`: `punycode.js@2.3.1` +- `Node.js`: `queue-microtask@1.2.3` +- `Node.js`: `react-compiler-runtime@1.0.0` +- `Node.js`: `react-day-picker@9.14.0` +- `Node.js`: `react-dom@19.2.5` +- `Node.js`: `react-error-boundary@6.1.2` +- `Node.js`: `react-is@17.0.2` +- `Node.js`: `react@19.2.5` +- `Node.js`: `readdirp@5.0.0` +- `Node.js`: `redent@3.0.0` +- `Node.js`: `regex-recursion@6.0.2` +- `Node.js`: `regex-utilities@2.3.0` +- `Node.js`: `regex@6.1.0` +- `Node.js`: `remeda@2.34.0` +- `Node.js`: `require-directory@2.1.1` +- `Node.js`: `require-from-string@2.0.2` +- `Node.js`: `rettime@0.11.8` +- `Node.js`: `reusify@1.1.0` +- `Node.js`: `rolldown@1.0.0-rc.17` +- `Node.js`: `rollup-plugin-visualizer@7.0.1` +- `Node.js`: `run-applescript@7.1.0` +- `Node.js`: `run-parallel@1.2.0` +- `Node.js`: `scheduler@0.27.0` +- `Node.js`: `seroval-plugins@1.5.4` +- `Node.js`: `seroval@1.5.4` +- `Node.js`: `set-cookie-parser@3.1.0` +- `Node.js`: `shebang-command@2.0.0` +- `Node.js`: `shebang-regex@3.0.0` +- `Node.js`: `shell-quote@1.8.3` +- `Node.js`: `slash@5.1.0` +- `Node.js`: `solid-js@1.9.12` +- `Node.js`: `space-separated-tokens@2.0.2` +- `Node.js`: `stackback@0.0.2` +- `Node.js`: `statuses@2.0.2` +- `Node.js`: `std-env@4.1.0` +- `Node.js`: `strict-event-emitter@0.5.1` +- `Node.js`: `string-argv@0.3.2` +- `Node.js`: `string-width@4.2.3` +- `Node.js`: `string-width@7.2.0` +- `Node.js`: `stringify-entities@4.0.4` +- `Node.js`: `strip-ansi@6.0.1` +- `Node.js`: `strip-ansi@7.2.0` +- `Node.js`: `strip-final-newline@4.0.0` +- `Node.js`: `strip-indent@3.0.0` +- `Node.js`: `supports-color@7.2.0` +- `Node.js`: `tagged-tag@1.0.0` +- `Node.js`: `tailwindcss@4.2.4` +- `Node.js`: `tapable@2.3.3` +- `Node.js`: `tinybench@2.9.0` +- `Node.js`: `tinyexec@1.1.2` +- `Node.js`: `tinyglobby@0.2.16` +- `Node.js`: `tinyrainbow@3.1.0` +- `Node.js`: `tldts-core@7.0.28` +- `Node.js`: `tldts@7.0.28` +- `Node.js`: `to-regex-range@5.0.1` +- `Node.js`: `trim-lines@3.0.1` +- `Node.js`: `tsconfck@3.1.6` +- `Node.js`: `typedoc-plugin-coverage@4.0.3` +- `Node.js`: `typedoc-plugin-markdown@4.11.0` +- `Node.js`: `uc.micro@2.1.0` +- `Node.js`: `undici-types@6.21.0` +- `Node.js`: `unicorn-magic@0.3.0` +- `Node.js`: `unicorn-magic@0.4.0` +- `Node.js`: `unist-util-is@6.0.1` +- `Node.js`: `unist-util-position@5.0.0` +- `Node.js`: `unist-util-stringify-position@4.0.0` +- `Node.js`: `unist-util-visit-parents@6.0.2` +- `Node.js`: `unist-util-visit@5.1.0` +- `Node.js`: `universalify@2.0.1` +- `Node.js`: `until-async@3.0.2` +- `Node.js`: `use-sync-external-store@1.6.0` +- `Node.js`: `util-deprecate@1.0.2` +- `Node.js`: `vfile-message@4.0.3` +- `Node.js`: `vfile@6.0.3` +- `Node.js`: `vite@8.0.10` +- `Node.js`: `vitest@4.1.5` +- `Node.js`: `whatwg-mimetype@3.0.0` +- `Node.js`: `why-is-node-running@2.3.0` +- `Node.js`: `wrap-ansi@7.0.0` +- `Node.js`: `wrap-ansi@9.0.2` +- `Node.js`: `ws@8.20.0` +- `Node.js`: `wsl-utils@0.3.1` +- `Node.js`: `yargs@17.7.2` +- `Node.js`: `yargs@18.0.0` +- `Node.js`: `yocto-queue@1.2.2` +- `Node.js`: `yoctocolors@2.1.2` +- `Node.js`: `zod@4.3.6` +- `Node.js`: `zwitch@2.0.4` - `Python`: `fastapi==0.110.0` - `Python`: `pyyaml` (`PyYAML>=5.4.1`, `pyyaml>=6.0.1`) - `Python`: `rich>=13.7.0` @@ -1351,6 +1871,15 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t ## MIT OR Apache-2.0 +- `Node.js`: `@biomejs/biome@2.4.5` +- `Node.js`: `@biomejs/cli-darwin-arm64@2.4.5` +- `Node.js`: `@biomejs/cli-darwin-x64@2.4.5` +- `Node.js`: `@biomejs/cli-linux-arm64-musl@2.4.5` +- `Node.js`: `@biomejs/cli-linux-arm64@2.4.5` +- `Node.js`: `@biomejs/cli-linux-x64-musl@2.4.5` +- `Node.js`: `@biomejs/cli-linux-x64@2.4.5` +- `Node.js`: `@biomejs/cli-win32-arm64@2.4.5` +- `Node.js`: `@biomejs/cli-win32-x64@2.4.5` - `Rust`: `ahash 0.8` - `Rust`: `anyhow` (`anyhow 1`, `anyhow 1.0`, `anyhow =1.0.103`) - `Rust`: `async-trait` (`async-trait 0.1`, `async-trait =0.1.89`) @@ -1436,6 +1965,26 @@ Generated by `go run -C ./tools/collect-dependencies .`. Refresh: `go run -C ./t - `Go`: `github.com/hashicorp/yamux` - `Go`: `github.com/shoenig/go-m1cpu` - `Helm`: `openbao 0.28.3 @ oci://ghcr.io/openbao/charts` +- `Node.js`: `lightningcss-android-arm64@1.32.0` +- `Node.js`: `lightningcss-darwin-arm64@1.32.0` +- `Node.js`: `lightningcss-darwin-x64@1.32.0` +- `Node.js`: `lightningcss-freebsd-x64@1.32.0` +- `Node.js`: `lightningcss-linux-arm-gnueabihf@1.32.0` +- `Node.js`: `lightningcss-linux-arm64-gnu@1.32.0` +- `Node.js`: `lightningcss-linux-arm64-musl@1.32.0` +- `Node.js`: `lightningcss-linux-x64-gnu@1.32.0` +- `Node.js`: `lightningcss-linux-x64-musl@1.32.0` +- `Node.js`: `lightningcss-win32-arm64-msvc@1.32.0` +- `Node.js`: `lightningcss-win32-x64-msvc@1.32.0` +- `Node.js`: `lightningcss@1.32.0` + +## Python-2.0 + +- `Node.js`: `argparse@2.0.1` + +## Unlicense + +- `Node.js`: `isbot@5.1.39` ## Unlicense OR MIT diff --git a/go.work.bazel b/go.work.bazel index 152f6b540..436e4c8b9 100644 --- a/go.work.bazel +++ b/go.work.bazel @@ -32,6 +32,7 @@ go 1.26.5 use ( ./src/clis/nvcf-cli + ./src/uis/nvcf-ui/backend ./src/libraries/go/lib ./src/libraries/go/worker ./src/compute-plane-services/nvsnap diff --git a/rules/oci/defs.bzl b/rules/oci/defs.bzl index db09768e7..b43c4397b 100644 --- a/rules/oci/defs.bzl +++ b/rules/oci/defs.bzl @@ -20,11 +20,13 @@ load( _go_oci_image = "go_oci_image", _go_oci_multi_binary_image = "go_oci_multi_binary_image", ) +load("//rules/oci/private:go_web.bzl", _go_web_oci_image = "go_web_oci_image") load("//rules/oci/private:rust.bzl", _rust_oci_image = "rust_oci_image") load("//rules/oci/private:java.bzl", _java_image_contract_test = "java_image_contract_test", _java_oci_image = "java_oci_image") go_oci_image = _go_oci_image go_oci_multi_binary_image = _go_oci_multi_binary_image +go_web_oci_image = _go_web_oci_image java_oci_image = _java_oci_image java_image_contract_test = _java_image_contract_test rust_oci_image = _rust_oci_image diff --git a/src/uis/nvcf-ui/rules/oci/private/go.bzl b/rules/oci/private/go_web.bzl similarity index 93% rename from src/uis/nvcf-ui/rules/oci/private/go.bzl rename to rules/oci/private/go_web.bzl index 0606cc2cf..1426c88a9 100644 --- a/src/uis/nvcf-ui/rules/oci/private/go.bzl +++ b/rules/oci/private/go_web.bzl @@ -30,7 +30,7 @@ _exec_files = rule( attrs = {"src": attr.label(cfg = "exec", allow_files = True)}, ) -def _go_web_oci_image_impl(name, visibility, binaries, web_assets, web_dir, package_dir, base, entrypoint, registry, tags, repo_tag, source_layer): +def _go_web_oci_image_impl(name, visibility, binaries, web_assets, web_dir, package_dir, base, entrypoint, registry, tags, source_layer): tars = [] # One layer per binary at {package_dir}/{basename}, e.g. /app/server. @@ -85,7 +85,6 @@ def _go_web_oci_image_impl(name, visibility, binaries, web_assets, web_dir, pack visibility = visibility, registry = registry, tags = tags, - repo_tag = repo_tag, ) go_web_oci_image = macro( @@ -132,12 +131,6 @@ go_web_oci_image = macro( doc = "Tags for generated targets. 'manual' is always added.", configurable = False, ), - "repo_tag": attr.string( - doc = "docker repo:tag for the _load target. Required at the repo " + - "root, where the package name is empty.", - default = "nvcf-ui:latest", - configurable = False, - ), "source_layer": attr.label( doc = "Optional pre-built pkg_tar target whose contents are added as " + "a layer in the image. Intended to bundle the repository source " + diff --git a/src/uis/nvcf-ui/.bazelignore b/src/uis/nvcf-ui/.bazelignore deleted file mode 100644 index a2fd38205..000000000 --- a/src/uis/nvcf-ui/.bazelignore +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Mirrors the gazelle:exclude entries in the root BUILD.bazel (needed because -# build //... / query load packages without consulting BUILD directives). - -ui/node_modules -ui/dist -ui/coverage -backend/static -backend/bin -node_modules -.bazel-cache diff --git a/src/uis/nvcf-ui/.bazelrc b/src/uis/nvcf-ui/.bazelrc deleted file mode 100644 index daee3ceaa..000000000 --- a/src/uis/nvcf-ui/.bazelrc +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Per-user overrides (not checked in). -try-import %workspace%/user.bazelrc -try-import %user.home%/.bazelrc.user - -build --compilation_mode=fastbuild -common --enable_bzlmod -build --incompatible_strict_action_env -common --enable_platform_specific_config - -# OCI tag stamping. Off by default; CI opts in with --stamp / --config=ci. -build --workspace_status_command=./tools/workspace_status.sh -build --nostamp - -test --test_output=errors -# Go code needs HOME; the sandbox strips it. Point it at /tmp. -test --test_env=HOME=/tmp -test --test_env=XDG_CACHE_HOME=/tmp/.cache - -startup --host_jvm_args=-Xmx4g - -build --@rules_go//go/config:pure - -build:ci --jobs=32 -build:ci --stamp - -# Remote cache is opt-in; add a build:remote profile to user.bazelrc. diff --git a/src/uis/nvcf-ui/.bazelversion b/src/uis/nvcf-ui/.bazelversion deleted file mode 100644 index acd405b1d..000000000 --- a/src/uis/nvcf-ui/.bazelversion +++ /dev/null @@ -1 +0,0 @@ -8.6.0 diff --git a/src/uis/nvcf-ui/BUILD.bazel b/src/uis/nvcf-ui/BUILD.bazel index 4bea41ac8..08c5ea1d4 100644 --- a/src/uis/nvcf-ui/BUILD.bazel +++ b/src/uis/nvcf-ui/BUILD.bazel @@ -32,8 +32,9 @@ load("//rules/oci:defs.bzl", "go_web_oci_image") gazelle(name = "gazelle") # Source files bundled into the container at /usr/share/nvcf-ui/src. -# Sub-packages (backend/, ui/, platforms/, rules/) are referenced via their +# Sub-packages (backend/, ui/) are referenced via their # own :sources filegroups because glob() does not cross package boundaries. +# platforms/ and rules/ are root build infrastructure and are not bundled. filegroup( name = "source_files", srcs = glob( @@ -57,10 +58,8 @@ filegroup( "MODULE.bazel.lock", ], ) + [ - "//backend:sources", - "//ui:sources", - "//platforms:sources", - "//rules/oci:sources", + "//src/uis/nvcf-ui/backend:sources", + "//src/uis/nvcf-ui/ui:sources", ], visibility = ["//visibility:private"], ) @@ -79,14 +78,14 @@ go_web_oci_image( name = "image", base = "@distroless_static", binaries = [ - "//backend/cmd/server:server", - "//backend/cmd/control-plane:control-plane", + "//src/uis/nvcf-ui/backend/cmd/server:server", + "//src/uis/nvcf-ui/backend/cmd/control-plane:control-plane", ], entrypoint = ["/app/server"], registry = "nvcr.io/0651155215864979/ncp-dev/nvcf-ui", source_layer = "//:source_layer", tags = ["nvcf-ui"], visibility = ["//visibility:public"], - web_assets = "//ui", + web_assets = "//src/uis/nvcf-ui/ui", web_dir = "/app/static", ) diff --git a/src/uis/nvcf-ui/MODULE.bazel b/src/uis/nvcf-ui/MODULE.bazel deleted file mode 100644 index 122440687..000000000 --- a/src/uis/nvcf-ui/MODULE.bazel +++ /dev/null @@ -1,102 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""nvcf-ui Bazel module. Mirrors the nvcf/nvcf umbrella so this repo is -Bazel-buildable standalone and drops into the umbrella on migration. -""" - -module( - name = "nvcf-ui", - version = "0.0.1", -) - -# ---- OCI (multi-arch) ---- -bazel_dep(name = "platforms", version = "1.0.0") -bazel_dep(name = "rules_oci", version = "2.2.7") -bazel_dep(name = "rules_pkg", version = "1.2.0") - -# 2.22.5 (not the umbrella's 2.19.3): aspect_rules_js 3.2.2 requires >= 2.22.5. -bazel_dep(name = "aspect_bazel_lib", version = "2.22.5") -bazel_dep(name = "hermetic_cc_toolchain", version = "4.1.0") - -# ---- Go ---- -bazel_dep(name = "rules_go", version = "0.60.0") -bazel_dep(name = "gazelle", version = "0.48.0") - -# rules_pkg's pkg_tar runs through a Python builder, which pulls in -# rules_python's hermetic interpreter. CI runs as root in the bazel-ci -# container, and rules_python rejects root-user setup by default -# (https://github.com/bazelbuild/rules_python/pull/713). -bazel_dep(name = "rules_python", version = "1.4.1") - -python = use_extension("@rules_python//python/extensions:python.bzl", "python") -python.toolchain( - ignore_root_user_error = True, - python_version = "3.11", -) - -go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") - -# 1.26.2 matches backend/go.mod (umbrella pins 1.25.0; reconcile on migration). -go_sdk.download(version = "1.26.2") - -go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") -go_deps.from_file(go_work = "//:go.work.bazel") - -# Managed by `bazel mod tidy`. -use_repo(go_deps, "com_github_fsnotify_fsnotify", "com_github_rs_zerolog", "in_yaml_go_yaml_v3", "io_k8s_api", "io_k8s_apimachinery", "io_k8s_client_go", "io_k8s_sigs_controller_runtime") - -# ---- Cross-compile toolchain (linux images from macOS/amd64 hosts) ---- -zig = use_extension("@hermetic_cc_toolchain//toolchain:ext.bzl", "toolchains") -use_repo(zig, "zig_sdk") - -register_toolchains( - "@zig_sdk//toolchain:linux_arm64_gnu.2.38", -) - -# ---- Base image: NVIDIA distroless-static (v4.0.0), pinned by digest ---- -oci = use_extension("@rules_oci//oci:extensions.bzl", "oci") -oci.pull( - name = "distroless_static", - digest = "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", - image = "nvcr.io/nvidia/distroless/static", - platforms = [ - "linux/amd64", - "linux/arm64/v8", - ], -) -use_repo( - oci, - "distroless_static", - "distroless_static_linux_amd64", - "distroless_static_linux_arm64_v8", -) - -# ---- JS/TS (aspect_rules_js, pnpm-native) ---- -# 3.2.x reads the build-script allowlist from pnpm-workspace.yaml (allowBuilds), -# so nothing in ui/ changes. -bazel_dep(name = "aspect_rules_js", version = "3.2.2") -bazel_dep(name = "rules_nodejs", version = "6.7.3") - -node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") -node.toolchain(node_version = "22.12.0") - -npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm") -npm.npm_translate_lock( - name = "npm", - pnpm_lock = "//ui:pnpm-lock.yaml", - verify_node_modules_ignored = "//:.bazelignore", -) -use_repo(npm, "npm") diff --git a/src/uis/nvcf-ui/MODULE.bazel.lock b/src/uis/nvcf-ui/MODULE.bazel.lock deleted file mode 100644 index 724e9a3f9..000000000 --- a/src/uis/nvcf-ui/MODULE.bazel.lock +++ /dev/null @@ -1,4081 +0,0 @@ -{ - "lockFileVersion": 24, - "registryFileHashes": { - "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", - "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", - "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", - "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/MODULE.bazel": "004ba890363d05372a97248c37205ae64b6fa31047629cd2c0895a9d0c7779e8", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "780d1a6522b28f5edb7ea09630748720721dfe27690d65a2d33aa7509de77e07", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", - "https://bcr.bazel.build/modules/aspect_rules_js/3.2.2/MODULE.bazel": "e844196321b64537cfade338f361633e7d5fe084eee3214fc531b9e7b9838813", - "https://bcr.bazel.build/modules/aspect_rules_js/3.2.2/source.json": "0e650e493b3bc784dc3f8240ff00b4b32258cde6d021ef6b1db4e387bcfd6a9a", - "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", - "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/source.json": "605086bbc197743a0d360f7ddc550a1d4dfa0441bc807236e17170f636153348", - "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", - "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", - "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", - "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", - "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", - "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", - "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", - "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", - "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", - "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", - "https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e", - "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", - "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", - "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", - "https://bcr.bazel.build/modules/bazel_features/1.41.0/source.json": "8fd525b31b0883c47e0593443cdd10219b94a7556b3195fc02d75c86c66cfe30", - "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", - "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", - "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", - "https://bcr.bazel.build/modules/bazel_lib/3.2.2/MODULE.bazel": "e2c890c8a515d6bca9c66d47718aa9e44b458fde64ec7204b8030bf2d349058c", - "https://bcr.bazel.build/modules/bazel_lib/3.2.2/source.json": "9e84e115c20e14652c5c21401ae85ff4daa8702e265b5c0b3bf89353f17aa212", - "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", - "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", - "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", - "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", - "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", - "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", - "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", - "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", - "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", - "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", - "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", - "https://bcr.bazel.build/modules/gazelle/0.48.0/MODULE.bazel": "ed89ed9e7e4f15a63434d553496088c5d02faa3dccc654331f3533d6542a9bb5", - "https://bcr.bazel.build/modules/gazelle/0.48.0/source.json": "30de037819207f7443d044c8873bb0b9137dd8ae8826bfb04ffe8a63e95a88dc", - "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", - "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", - "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", - "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.1.0/MODULE.bazel": "b512c9698968bd4548eecec8fb9bab4327c6f50fb0d12cc56e9a4d32a9a78458", - "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.1.0/source.json": "25ec6993f3acde436250883f3ab7945ff90a2fe42407af2c66f65f0f3e5b2959", - "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", - "https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2", - "https://bcr.bazel.build/modules/jq.bzl/0.4.0/source.json": "52ec7530c4618e03f634b30ff719814a68d7d39c235938b7aa2abbfe1eb1c52c", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", - "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", - "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", - "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", - "https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85", - "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", - "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", - "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", - "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", - "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", - "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", - "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", - "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", - "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", - "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", - "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", - "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", - "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", - "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", - "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", - "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", - "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", - "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", - "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", - "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", - "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", - "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", - "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", - "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", - "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", - "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", - "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", - "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", - "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", - "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", - "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", - "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", - "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", - "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", - "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", - "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", - "https://bcr.bazel.build/modules/rules_cc/0.2.16/source.json": "d03d5cde49376d87e14ec14b666c56075e5e3926930327fd5d0484a1ff2ac1cc", - "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", - "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", - "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", - "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", - "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", - "https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae", - "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", - "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", - "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", - "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", - "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", - "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", - "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", - "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", - "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", - "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", - "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", - "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", - "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", - "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", - "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", - "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", - "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", - "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", - "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", - "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", - "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", - "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", - "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", - "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", - "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", - "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", - "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09", - "https://bcr.bazel.build/modules/rules_oci/2.2.7/MODULE.bazel": "f6150e4b224d459f7f6523ef65967464ca4efdd266c7fbf2f5a2a51011957e0c", - "https://bcr.bazel.build/modules/rules_oci/2.2.7/source.json": "b099f02af330f47f19dc67fc9300ef6e1937a8c86882690db0e7a2fcea8c7f6b", - "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", - "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", - "https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e", - "https://bcr.bazel.build/modules/rules_pkg/1.2.0/source.json": "9062e00845bf91a4247465d371baa837adf9b6ff44c542f73ba084f07667e1dc", - "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", - "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", - "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", - "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", - "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", - "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", - "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", - "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", - "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", - "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", - "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", - "https://bcr.bazel.build/modules/rules_python/1.4.1/source.json": "8ec8c90c70ccacc4de8ca1b97f599e756fb59173e898ee08b733006650057c07", - "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", - "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", - "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", - "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", - "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", - "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", - "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", - "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", - "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", - "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", - "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", - "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", - "https://bcr.bazel.build/modules/tar.bzl/0.10.4/source.json": "20143442376c03426f6135292ba02d825cb75308aa47e6bf42dd4cc5a435c2ff", - "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", - "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", - "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", - "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", - "https://bcr.bazel.build/modules/yq.bzl/0.3.4/MODULE.bazel": "d3a270662f5d766cd7229732d65a5a5bc485240c3007343dd279edfb60c9ae27", - "https://bcr.bazel.build/modules/yq.bzl/0.3.4/source.json": "786dafdc2843722da3416e4343ee1a05237227f068590779a6e8496a2064c0f9", - "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", - "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", - "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" - }, - "selectedYankedVersions": {}, - "moduleExtensions": { - "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { - "general": { - "bzlTransitiveDigest": "rWK8ttAnVBnBw1jwn8neIz9bZuiX/91Bkq/QCucF2u8=", - "usagesDigest": "v0BUZKb/gZBwBhCczerRGtre1VqU53gx1lLMtW5B+fs=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "pnpm": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", - "attributes": { - "package": "pnpm", - "version": "8.6.7", - "root_package": "", - "link_workspace": "", - "link_packages": {}, - "integrity": "sha512-vRIWpD/L4phf9Bk2o/O2TDR8fFoJnpYrp2TKqTIZF/qZ2/rgL3qKXzHofHgbXsinwMoSEigz28sqk3pQ+yMEQQ==", - "url": "", - "commit": "", - "patch_args": [ - "-p0" - ], - "patches": [], - "custom_postinstall": "", - "npm_auth": "", - "npm_auth_basic": "", - "npm_auth_username": "", - "npm_auth_password": "", - "lifecycle_hooks": [], - "extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])", - "generate_bzl_library_targets": false, - "extract_full_archive": true, - "exclude_package_contents": [], - "system_tar": "auto" - } - }, - "pnpm__links": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", - "attributes": { - "package": "pnpm", - "version": "8.6.7", - "dev": false, - "root_package": "", - "link_packages": {}, - "deps": {}, - "transitive_closure": {}, - "lifecycle_build_target": false, - "lifecycle_hooks_env": [], - "lifecycle_hooks_execution_requirements": [ - "no-sandbox" - ], - "lifecycle_hooks_use_default_shell_env": false, - "bins": {}, - "package_visibility": [ - "//visibility:public" - ], - "replace_package": "", - "exclude_package_contents": [] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "aspect_bazel_lib+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "aspect_bazel_lib+", - "bazel_tools", - "bazel_tools" - ], - [ - "aspect_bazel_lib+", - "tar.bzl", - "tar.bzl+" - ], - [ - "aspect_rules_js+", - "aspect_bazel_lib", - "aspect_bazel_lib+" - ], - [ - "aspect_rules_js+", - "aspect_rules_js", - "aspect_rules_js+" - ], - [ - "aspect_rules_js+", - "bazel_features", - "bazel_features+" - ], - [ - "aspect_rules_js+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "aspect_rules_js+", - "bazel_tools", - "bazel_tools" - ], - [ - "bazel_features+", - "bazel_features_globals", - "bazel_features++version_extension+bazel_features_globals" - ], - [ - "bazel_features+", - "bazel_features_version", - "bazel_features++version_extension+bazel_features_version" - ], - [ - "tar.bzl+", - "aspect_bazel_lib", - "aspect_bazel_lib+" - ], - [ - "tar.bzl+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "tar.bzl+", - "tar.bzl", - "tar.bzl+" - ] - ] - } - }, - "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { - "general": { - "bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=", - "usagesDigest": "qAHU62OXACzqqTdD33AhM8HizjWUgO22B+s6YCegb5I=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "aspect_tools_telemetry_report": { - "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", - "attributes": { - "deps": { - "aspect_rules_js": "3.2.2", - "aspect_tools_telemetry": "0.3.3" - } - } - } - }, - "recordedRepoMappingEntries": [ - [ - "aspect_tools_telemetry+", - "bazel_lib", - "bazel_lib+" - ], - [ - "aspect_tools_telemetry+", - "bazel_skylib", - "bazel_skylib+" - ] - ] - } - }, - "@@pybind11_bazel+//:python_configure.bzl%extension": { - "general": { - "bzlTransitiveDigest": "D2/qWHU6yQFwRG7Bb+caqrYMha5avsASao2vERrxK24=", - "usagesDigest": "fycyB39YnXIJkfWCIXLUKJMZzANcuLy9ZE73hRucjFk=", - "recordedFileInputs": { - "@@pybind11_bazel+//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" - }, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "local_config_python": { - "repoRuleId": "@@pybind11_bazel+//:python_configure.bzl%python_configure", - "attributes": {} - }, - "pybind11": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@pybind11_bazel+//:pybind11.BUILD", - "strip_prefix": "pybind11-2.11.1", - "urls": [ - "https://github.com/pybind/pybind11/archive/v2.11.1.zip" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "pybind11_bazel+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { - "general": { - "bzlTransitiveDigest": "4LouzhF/yT117s7peGnNs9ROomiJXC6Zl5R0oI21jho=", - "usagesDigest": "wy6ISK6UOcBEjj/mvJ/S3WeXoO67X+1llb9yPyFtPgc=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "platforms": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz", - "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz" - ], - "sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74" - } - }, - "rules_python": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8", - "strip_prefix": "rules_python-0.28.0", - "url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz" - } - }, - "bazel_skylib": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", - "urls": [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz" - ] - } - }, - "com_google_absl": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip" - ], - "strip_prefix": "abseil-cpp-20240116.1", - "integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=" - } - }, - "rules_fuzzing_oss_fuzz": { - "repoRuleId": "@@rules_fuzzing+//fuzzing/private/oss_fuzz:repository.bzl%oss_fuzz_repository", - "attributes": {} - }, - "honggfuzz": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@rules_fuzzing+//:honggfuzz.BUILD", - "sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e", - "url": "https://github.com/google/honggfuzz/archive/2.5.zip", - "strip_prefix": "honggfuzz-2.5" - } - }, - "rules_fuzzing_jazzer": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2", - "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar" - } - }, - "rules_fuzzing_jazzer_api": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b", - "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_fuzzing+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { - "general": { - "bzlTransitiveDigest": "nvW/NrBXlAmiQw99EMGKkLaD2KbNp2mQDlxdfpr+0Ls=", - "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "com_github_jetbrains_kotlin_git": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", - "attributes": { - "urls": [ - "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" - ], - "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" - } - }, - "com_github_jetbrains_kotlin": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", - "attributes": { - "git_repository_name": "com_github_jetbrains_kotlin_git", - "compiler_version": "1.9.23" - } - }, - "com_github_google_ksp": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", - "attributes": { - "urls": [ - "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" - ], - "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", - "strip_version": "1.9.23-1.0.20" - } - }, - "com_github_pinterest_ktlint": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", - "attributes": { - "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", - "urls": [ - "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" - ], - "executable": true - } - }, - "rules_android": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", - "strip_prefix": "rules_android-0.1.1", - "urls": [ - "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_kotlin+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_nodejs+//nodejs:extensions.bzl%node": { - "general": { - "bzlTransitiveDigest": "4pUxCNc22K4I+6+4Nxu52Hur12tFRfa1JMsN5mdDv60=", - "usagesDigest": "uKJ3CNzyy206RpWtEiKgCgUB2gcgUJW0m3u9oo5lczk=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "nodejs_linux_amd64": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "linux_amd64" - } - }, - "nodejs_linux_arm64": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "linux_arm64" - } - }, - "nodejs_linux_s390x": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "linux_s390x" - } - }, - "nodejs_linux_ppc64le": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "linux_ppc64le" - } - }, - "nodejs_darwin_amd64": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "darwin_amd64" - } - }, - "nodejs_darwin_arm64": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "darwin_arm64" - } - }, - "nodejs_windows_amd64": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "windows_amd64" - } - }, - "nodejs_windows_arm64": { - "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", - "attributes": { - "node_download_auth": {}, - "node_repositories": {}, - "node_urls": [ - "https://nodejs.org/dist/v{version}/{filename}" - ], - "node_version": "22.12.0", - "include_headers": false, - "platform": "windows_arm64" - } - }, - "nodejs": { - "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", - "attributes": { - "user_node_repository_name": "nodejs" - } - }, - "nodejs_host": { - "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", - "attributes": { - "user_node_repository_name": "nodejs" - } - }, - "nodejs_toolchains": { - "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", - "attributes": { - "user_node_repository_name": "nodejs" - } - } - }, - "recordedRepoMappingEntries": [] - } - }, - "@@rules_oci+//oci:extensions.bzl%oci": { - "general": { - "bzlTransitiveDigest": "yxz4K6WSZfcCM1gDZw3dXbIYnUQfr4mOz4RPmu33NNU=", - "usagesDigest": "dtYTY4KBFDrNH3krF/vE3VC/hdKMrrtT3NGgym2X7gM=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "distroless_static_linux_amd64": { - "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_pull", - "attributes": { - "www_authenticate_challenges": {}, - "scheme": "https", - "registry": "nvcr.io", - "repository": "nvidia/distroless/static", - "identifier": "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", - "platform": "linux/amd64", - "target_name": "distroless_static_linux_amd64", - "bazel_tags": [] - } - }, - "distroless_static_linux_arm64_v8": { - "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_pull", - "attributes": { - "www_authenticate_challenges": {}, - "scheme": "https", - "registry": "nvcr.io", - "repository": "nvidia/distroless/static", - "identifier": "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", - "platform": "linux/arm64/v8", - "target_name": "distroless_static_linux_arm64_v8", - "bazel_tags": [] - } - }, - "distroless_static": { - "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_alias", - "attributes": { - "target_name": "distroless_static", - "www_authenticate_challenges": {}, - "scheme": "https", - "registry": "nvcr.io", - "repository": "nvidia/distroless/static", - "identifier": "sha256:d90158b69e250d2018f32622b5c622925202ee97224a990a54b63811cb1e3d69", - "platforms": { - "@@platforms//cpu:x86_64": "@distroless_static_linux_amd64", - "@@platforms//cpu:arm64": "@distroless_static_linux_arm64_v8" - }, - "bzlmod_repository": "distroless_static", - "reproducible": true - } - }, - "oci_crane_darwin_amd64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "darwin_amd64", - "crane_version": "v0.18.0" - } - }, - "oci_crane_darwin_arm64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "darwin_arm64", - "crane_version": "v0.18.0" - } - }, - "oci_crane_linux_arm64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "linux_arm64", - "crane_version": "v0.18.0" - } - }, - "oci_crane_linux_armv6": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "linux_armv6", - "crane_version": "v0.18.0" - } - }, - "oci_crane_linux_i386": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "linux_i386", - "crane_version": "v0.18.0" - } - }, - "oci_crane_linux_s390x": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "linux_s390x", - "crane_version": "v0.18.0" - } - }, - "oci_crane_linux_amd64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "linux_amd64", - "crane_version": "v0.18.0" - } - }, - "oci_crane_windows_armv6": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "windows_armv6", - "crane_version": "v0.18.0" - } - }, - "oci_crane_windows_amd64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", - "attributes": { - "platform": "windows_amd64", - "crane_version": "v0.18.0" - } - }, - "oci_crane_toolchains": { - "repoRuleId": "@@rules_oci+//oci/private:toolchains_repo.bzl%toolchains_repo", - "attributes": { - "toolchain_type": "@rules_oci//oci:crane_toolchain_type", - "toolchain": "@oci_crane_{platform}//:crane_toolchain" - } - }, - "oci_regctl_darwin_amd64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", - "attributes": { - "platform": "darwin_amd64" - } - }, - "oci_regctl_darwin_arm64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", - "attributes": { - "platform": "darwin_arm64" - } - }, - "oci_regctl_linux_arm64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", - "attributes": { - "platform": "linux_arm64" - } - }, - "oci_regctl_linux_s390x": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", - "attributes": { - "platform": "linux_s390x" - } - }, - "oci_regctl_linux_amd64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", - "attributes": { - "platform": "linux_amd64" - } - }, - "oci_regctl_windows_amd64": { - "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", - "attributes": { - "platform": "windows_amd64" - } - }, - "oci_regctl_toolchains": { - "repoRuleId": "@@rules_oci+//oci/private:toolchains_repo.bzl%toolchains_repo", - "attributes": { - "toolchain_type": "@rules_oci//oci:regctl_toolchain_type", - "toolchain": "@oci_regctl_{platform}//:regctl_toolchain" - } - } - }, - "moduleExtensionMetadata": { - "explicitRootModuleDirectDeps": [ - "distroless_static", - "distroless_static_linux_amd64", - "distroless_static_linux_arm64_v8" - ], - "explicitRootModuleDirectDevDeps": [], - "useAllRepos": "NO", - "reproducible": false - }, - "recordedRepoMappingEntries": [ - [ - "aspect_bazel_lib+", - "bazel_tools", - "bazel_tools" - ], - [ - "bazel_features+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_oci+", - "aspect_bazel_lib", - "aspect_bazel_lib+" - ], - [ - "rules_oci+", - "bazel_features", - "bazel_features+" - ], - [ - "rules_oci+", - "bazel_skylib", - "bazel_skylib+" - ] - ] - } - }, - "@@rules_python+//python/extensions:pip.bzl%pip": { - "general": { - "bzlTransitiveDigest": "C2LrqfYKhXdWDlSvRVaXkVKMOIxzPM30HuJKZEvbGko=", - "usagesDigest": "/9NP3RV6/DWuNdYAsIU/8UCgCX0TdPUJr0X6O+0lrtk=", - "recordedFileInputs": { - "@@protobuf+//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5", - "@@rules_fuzzing+//fuzzing/requirements.txt": "ab04664be026b632a0d2a2446c4f65982b7654f5b6851d2f9d399a19b7242a5b", - "@@rules_python+//tools/publish/requirements_darwin.txt": "2994136eab7e57b083c3de76faf46f70fad130bc8e7360a7fed2b288b69e79dc", - "@@rules_python+//tools/publish/requirements_linux.txt": "8175b4c8df50ae2f22d1706961884beeb54e7da27bd2447018314a175981997d", - "@@rules_python+//tools/publish/requirements_windows.txt": "7673adc71dc1a81d3661b90924d7a7c0fc998cd508b3cb4174337cef3f2de556" - }, - "recordedDirentsInputs": {}, - "envVariables": { - "RULES_PYTHON_REPO_DEBUG": null, - "RULES_PYTHON_REPO_DEBUG_VERBOSITY": null - }, - "generatedRepoSpecs": { - "pip_deps_310_numpy": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", - "repo": "pip_deps_310", - "requirement": "numpy<=1.26.1" - } - }, - "pip_deps_310_setuptools": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", - "repo": "pip_deps_310", - "requirement": "setuptools<=70.3.0" - } - }, - "pip_deps_311_numpy": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "pip_deps_311", - "requirement": "numpy<=1.26.1" - } - }, - "pip_deps_311_setuptools": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "pip_deps_311", - "requirement": "setuptools<=70.3.0" - } - }, - "pip_deps_312_numpy": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", - "repo": "pip_deps_312", - "requirement": "numpy<=1.26.1" - } - }, - "pip_deps_312_setuptools": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", - "repo": "pip_deps_312", - "requirement": "setuptools<=70.3.0" - } - }, - "pip_deps_38_numpy": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", - "repo": "pip_deps_38", - "requirement": "numpy<=1.26.1" - } - }, - "pip_deps_38_setuptools": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", - "repo": "pip_deps_38", - "requirement": "setuptools<=70.3.0" - } - }, - "pip_deps_39_numpy": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", - "repo": "pip_deps_39", - "requirement": "numpy<=1.26.1" - } - }, - "pip_deps_39_setuptools": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@pip_deps//{name}:{target}", - "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", - "repo": "pip_deps_39", - "requirement": "setuptools<=70.3.0" - } - }, - "rules_fuzzing_py_deps_310_absl_py": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", - "repo": "rules_fuzzing_py_deps_310", - "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" - } - }, - "rules_fuzzing_py_deps_310_six": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", - "repo": "rules_fuzzing_py_deps_310", - "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - } - }, - "rules_fuzzing_py_deps_311_absl_py": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_fuzzing_py_deps_311", - "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" - } - }, - "rules_fuzzing_py_deps_311_six": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_fuzzing_py_deps_311", - "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - } - }, - "rules_fuzzing_py_deps_312_absl_py": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", - "repo": "rules_fuzzing_py_deps_312", - "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" - } - }, - "rules_fuzzing_py_deps_312_six": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", - "repo": "rules_fuzzing_py_deps_312", - "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - } - }, - "rules_fuzzing_py_deps_38_absl_py": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", - "repo": "rules_fuzzing_py_deps_38", - "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" - } - }, - "rules_fuzzing_py_deps_38_six": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", - "repo": "rules_fuzzing_py_deps_38", - "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - } - }, - "rules_fuzzing_py_deps_39_absl_py": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", - "repo": "rules_fuzzing_py_deps_39", - "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" - } - }, - "rules_fuzzing_py_deps_39_six": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", - "extra_pip_args": [ - "--require-hashes" - ], - "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", - "repo": "rules_fuzzing_py_deps_39", - "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - } - }, - "rules_python_publish_deps_311_backports_tarfile_py3_none_any_77e284d7": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "backports.tarfile-1.2.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "backports-tarfile==1.2.0", - "sha256": "77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", - "urls": [ - "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_backports_tarfile_sdist_d75e02c2": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "backports_tarfile-1.2.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "backports-tarfile==1.2.0", - "sha256": "d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", - "urls": [ - "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_certifi_py3_none_any_922820b5": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "certifi-2024.8.30-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "certifi==2024.8.30", - "sha256": "922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "urls": [ - "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_certifi_sdist_bec941d2": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "certifi-2024.8.30.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "certifi==2024.8.30", - "sha256": "bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", - "urls": [ - "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_a1ed2dd2": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cffi==1.17.1", - "sha256": "a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", - "urls": [ - "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_ppc64le_46bf4316": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cffi==1.17.1", - "sha256": "46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", - "urls": [ - "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" - ] - } - }, - "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_s390x_a24ed04c": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cffi==1.17.1", - "sha256": "a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", - "urls": [ - "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" - ] - } - }, - "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_x86_64_610faea7": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cffi==1.17.1", - "sha256": "610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", - "urls": [ - "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_aarch64_a9b15d49": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cffi==1.17.1", - "sha256": "a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", - "urls": [ - "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_x86_64_fc48c783": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cffi==1.17.1", - "sha256": "fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", - "urls": [ - "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_cffi_sdist_1c39c601": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "cffi-1.17.1.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cffi==1.17.1", - "sha256": "1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", - "urls": [ - "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_universal2_0d99dd8f": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", - "urls": [ - "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_x86_64_c57516e5": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", - "urls": [ - "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_11_0_arm64_6dba5d19": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", - "urls": [ - "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_aarch64_bf4475b8": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", - "urls": [ - "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_ppc64le_ce031db0": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", - "urls": [ - "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_s390x_8ff4e7cd": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", - "urls": [ - "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_x86_64_3710a975": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", - "urls": [ - "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_aarch64_47334db7": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", - "urls": [ - "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_ppc64le_f1a2f519": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", - "urls": [ - "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_s390x_63bc5c4a": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", - "urls": [ - "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_x86_64_bcb4f8ea": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", - "urls": [ - "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_win_amd64_cee4373f": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", - "urls": [ - "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_py3_none_any_fe9f97fe": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "charset_normalizer-3.4.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", - "urls": [ - "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_charset_normalizer_sdist_223217c3": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "charset_normalizer-3.4.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "charset-normalizer==3.4.0", - "sha256": "223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", - "urls": [ - "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_aarch64_846da004": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cryptography==43.0.3", - "sha256": "846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", - "urls": [ - "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_x86_64_0f996e72": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cryptography==43.0.3", - "sha256": "0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", - "urls": [ - "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_aarch64_f7b178f1": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cryptography==43.0.3", - "sha256": "f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", - "urls": [ - "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_x86_64_c2e6fc39": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cryptography==43.0.3", - "sha256": "c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", - "urls": [ - "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_aarch64_e1be4655": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cryptography==43.0.3", - "sha256": "e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", - "urls": [ - "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_x86_64_df6b6c6d": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cryptography==43.0.3", - "sha256": "df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", - "urls": [ - "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_cryptography_sdist_315b9001": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "cryptography-43.0.3.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "cryptography==43.0.3", - "sha256": "315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", - "urls": [ - "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_docutils_py3_none_any_dafca5b9": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "docutils-0.21.2-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "docutils==0.21.2", - "sha256": "dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", - "urls": [ - "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_docutils_sdist_3a6b1873": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "docutils-0.21.2.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "docutils==0.21.2", - "sha256": "3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", - "urls": [ - "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_idna_py3_none_any_946d195a": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "idna-3.10-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "idna==3.10", - "sha256": "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", - "urls": [ - "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_idna_sdist_12f65c9b": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "idna-3.10.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "idna==3.10", - "sha256": "12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", - "urls": [ - "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_importlib_metadata_py3_none_any_45e54197": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "importlib_metadata-8.5.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "importlib-metadata==8.5.0", - "sha256": "45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", - "urls": [ - "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_importlib_metadata_sdist_71522656": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "importlib_metadata-8.5.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "importlib-metadata==8.5.0", - "sha256": "71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", - "urls": [ - "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_jaraco_classes_py3_none_any_f662826b": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "jaraco.classes-3.4.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jaraco-classes==3.4.0", - "sha256": "f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", - "urls": [ - "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_jaraco_classes_sdist_47a024b5": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "jaraco.classes-3.4.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jaraco-classes==3.4.0", - "sha256": "47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", - "urls": [ - "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_jaraco_context_py3_none_any_f797fc48": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "jaraco.context-6.0.1-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jaraco-context==6.0.1", - "sha256": "f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", - "urls": [ - "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_jaraco_context_sdist_9bae4ea5": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "jaraco_context-6.0.1.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jaraco-context==6.0.1", - "sha256": "9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", - "urls": [ - "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_jaraco_functools_py3_none_any_ad159f13": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "jaraco.functools-4.1.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jaraco-functools==4.1.0", - "sha256": "ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", - "urls": [ - "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_jaraco_functools_sdist_70f7e0e2": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "jaraco_functools-4.1.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jaraco-functools==4.1.0", - "sha256": "70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", - "urls": [ - "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_jeepney_py3_none_any_c0a454ad": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "jeepney-0.8.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jeepney==0.8.0", - "sha256": "c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755", - "urls": [ - "https://files.pythonhosted.org/packages/ae/72/2a1e2290f1ab1e06f71f3d0f1646c9e4634e70e1d37491535e19266e8dc9/jeepney-0.8.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_jeepney_sdist_5efe48d2": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "jeepney-0.8.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "jeepney==0.8.0", - "sha256": "5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", - "urls": [ - "https://files.pythonhosted.org/packages/d6/f4/154cf374c2daf2020e05c3c6a03c91348d59b23c5366e968feb198306fdf/jeepney-0.8.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_keyring_py3_none_any_5426f817": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "keyring-25.4.1-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "keyring==25.4.1", - "sha256": "5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf", - "urls": [ - "https://files.pythonhosted.org/packages/83/25/e6d59e5f0a0508d0dca8bb98c7f7fd3772fc943ac3f53d5ab18a218d32c0/keyring-25.4.1-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_keyring_sdist_b07ebc55": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "keyring-25.4.1.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "keyring==25.4.1", - "sha256": "b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b", - "urls": [ - "https://files.pythonhosted.org/packages/a5/1c/2bdbcfd5d59dc6274ffb175bc29aa07ecbfab196830e0cfbde7bd861a2ea/keyring-25.4.1.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_markdown_it_py_py3_none_any_35521684": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "markdown_it_py-3.0.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "markdown-it-py==3.0.0", - "sha256": "355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", - "urls": [ - "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_markdown_it_py_sdist_e3f60a94": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "markdown-it-py-3.0.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "markdown-it-py==3.0.0", - "sha256": "e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", - "urls": [ - "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_mdurl_py3_none_any_84008a41": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "mdurl-0.1.2-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "mdurl==0.1.2", - "sha256": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", - "urls": [ - "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_mdurl_sdist_bb413d29": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "mdurl-0.1.2.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "mdurl==0.1.2", - "sha256": "bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", - "urls": [ - "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_more_itertools_py3_none_any_037b0d32": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "more_itertools-10.5.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "more-itertools==10.5.0", - "sha256": "037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", - "urls": [ - "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_more_itertools_sdist_5482bfef": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "more-itertools-10.5.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "more-itertools==10.5.0", - "sha256": "5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", - "urls": [ - "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_14c5a72e": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86", - "urls": [ - "https://files.pythonhosted.org/packages/b3/89/1daff5d9ba5a95a157c092c7c5f39b8dd2b1ddb4559966f808d31cfb67e0/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_7b7c2a3c": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811", - "urls": [ - "https://files.pythonhosted.org/packages/2c/b6/42fc3c69cabf86b6b81e4c051a9b6e249c5ba9f8155590222c2622961f58/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_aarch64_42c64511": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200", - "urls": [ - "https://files.pythonhosted.org/packages/45/b9/833f385403abaf0023c6547389ec7a7acf141ddd9d1f21573723a6eab39a/nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_armv7l_0411beb0": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164", - "urls": [ - "https://files.pythonhosted.org/packages/05/2b/85977d9e11713b5747595ee61f381bc820749daf83f07b90b6c9964cf932/nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64_5f36b271": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189", - "urls": [ - "https://files.pythonhosted.org/packages/72/f2/5c894d5265ab80a97c68ca36f25c8f6f0308abac649aaf152b74e7e854a8/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64le_34c03fa7": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad", - "urls": [ - "https://files.pythonhosted.org/packages/ab/a7/375afcc710dbe2d64cfbd69e31f82f3e423d43737258af01f6a56d844085/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_s390x_19aaba96": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b", - "urls": [ - "https://files.pythonhosted.org/packages/c2/a8/3bb02d0c60a03ad3a112b76c46971e9480efa98a8946677b5a59f60130ca/nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_x86_64_de3ceed6": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307", - "urls": [ - "https://files.pythonhosted.org/packages/1b/63/6ab90d0e5225ab9780f6c9fb52254fa36b52bb7c188df9201d05b647e5e1/nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_aarch64_f0eca9ca": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe", - "urls": [ - "https://files.pythonhosted.org/packages/a3/da/0c4e282bc3cff4a0adf37005fa1fb42257673fbc1bbf7d1ff639ec3d255a/nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_armv7l_3a157ab1": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a", - "urls": [ - "https://files.pythonhosted.org/packages/de/81/c291231463d21da5f8bba82c8167a6d6893cc5419b0639801ee5d3aeb8a9/nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_x86_64_36c95d4b": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204", - "urls": [ - "https://files.pythonhosted.org/packages/eb/61/73a007c74c37895fdf66e0edcd881f5eaa17a348ff02f4bb4bc906d61085/nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_cp37_abi3_win_amd64_8ce0f819": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "nh3-0.2.18-cp37-abi3-win_amd64.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844", - "urls": [ - "https://files.pythonhosted.org/packages/26/8d/53c5b19c4999bdc6ba95f246f4ef35ca83d7d7423e5e38be43ad66544e5d/nh3-0.2.18-cp37-abi3-win_amd64.whl" - ] - } - }, - "rules_python_publish_deps_311_nh3_sdist_94a16692": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "nh3-0.2.18.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "nh3==0.2.18", - "sha256": "94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4", - "urls": [ - "https://files.pythonhosted.org/packages/62/73/10df50b42ddb547a907deeb2f3c9823022580a7a47281e8eae8e003a9639/nh3-0.2.18.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_pkginfo_py3_none_any_889a6da2": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "pkginfo-1.10.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pkginfo==1.10.0", - "sha256": "889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097", - "urls": [ - "https://files.pythonhosted.org/packages/56/09/054aea9b7534a15ad38a363a2bd974c20646ab1582a387a95b8df1bfea1c/pkginfo-1.10.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_pkginfo_sdist_5df73835": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "pkginfo-1.10.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pkginfo==1.10.0", - "sha256": "5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", - "urls": [ - "https://files.pythonhosted.org/packages/2f/72/347ec5be4adc85c182ed2823d8d1c7b51e13b9a6b0c1aae59582eca652df/pkginfo-1.10.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_pycparser_py3_none_any_c3702b6d": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "pycparser-2.22-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pycparser==2.22", - "sha256": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", - "urls": [ - "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_pycparser_sdist_491c8be9": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "pycparser-2.22.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pycparser==2.22", - "sha256": "491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", - "urls": [ - "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_pygments_py3_none_any_b8e6aca0": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "pygments-2.18.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pygments==2.18.0", - "sha256": "b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", - "urls": [ - "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_pygments_sdist_786ff802": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "pygments-2.18.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pygments==2.18.0", - "sha256": "786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", - "urls": [ - "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_pywin32_ctypes_py3_none_any_8a151337": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_windows_x86_64" - ], - "filename": "pywin32_ctypes-0.2.3-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pywin32-ctypes==0.2.3", - "sha256": "8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", - "urls": [ - "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_pywin32_ctypes_sdist_d162dc04": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "pywin32-ctypes-0.2.3.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "pywin32-ctypes==0.2.3", - "sha256": "d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", - "urls": [ - "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_readme_renderer_py3_none_any_2fbca89b": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "readme_renderer-44.0-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "readme-renderer==44.0", - "sha256": "2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", - "urls": [ - "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_readme_renderer_sdist_8712034e": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "readme_renderer-44.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "readme-renderer==44.0", - "sha256": "8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", - "urls": [ - "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_requests_py3_none_any_70761cfe": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "requests-2.32.3-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "requests==2.32.3", - "sha256": "70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", - "urls": [ - "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_requests_sdist_55365417": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "requests-2.32.3.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "requests==2.32.3", - "sha256": "55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", - "urls": [ - "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_requests_toolbelt_py2_none_any_cccfdd66": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "requests_toolbelt-1.0.0-py2.py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "requests-toolbelt==1.0.0", - "sha256": "cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", - "urls": [ - "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_requests_toolbelt_sdist_7681a0a3": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "requests-toolbelt-1.0.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "requests-toolbelt==1.0.0", - "sha256": "7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", - "urls": [ - "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_rfc3986_py2_none_any_50b1502b": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "rfc3986-2.0.0-py2.py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "rfc3986==2.0.0", - "sha256": "50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", - "urls": [ - "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_rfc3986_sdist_97aacf9d": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "rfc3986-2.0.0.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "rfc3986==2.0.0", - "sha256": "97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", - "urls": [ - "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_rich_py3_none_any_9836f509": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "rich-13.9.3-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "rich==13.9.3", - "sha256": "9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283", - "urls": [ - "https://files.pythonhosted.org/packages/9a/e2/10e9819cf4a20bd8ea2f5dabafc2e6bf4a78d6a0965daeb60a4b34d1c11f/rich-13.9.3-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_rich_sdist_bc1e01b8": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "rich-13.9.3.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "rich==13.9.3", - "sha256": "bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e", - "urls": [ - "https://files.pythonhosted.org/packages/d9/e9/cf9ef5245d835065e6673781dbd4b8911d352fb770d56cf0879cf11b7ee1/rich-13.9.3.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_secretstorage_py3_none_any_f356e662": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "filename": "SecretStorage-3.3.3-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "secretstorage==3.3.3", - "sha256": "f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", - "urls": [ - "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_secretstorage_sdist_2403533e": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "SecretStorage-3.3.3.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "secretstorage==3.3.3", - "sha256": "2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", - "urls": [ - "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_twine_py3_none_any_215dbe7b": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "twine-5.1.1-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "twine==5.1.1", - "sha256": "215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", - "urls": [ - "https://files.pythonhosted.org/packages/5d/ec/00f9d5fd040ae29867355e559a94e9a8429225a0284a3f5f091a3878bfc0/twine-5.1.1-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_twine_sdist_9aa08251": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "twine-5.1.1.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "twine==5.1.1", - "sha256": "9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db", - "urls": [ - "https://files.pythonhosted.org/packages/77/68/bd982e5e949ef8334e6f7dcf76ae40922a8750aa2e347291ae1477a4782b/twine-5.1.1.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_urllib3_py3_none_any_ca899ca0": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "urllib3-2.2.3-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "urllib3==2.2.3", - "sha256": "ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", - "urls": [ - "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_urllib3_sdist_e7d814a8": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "urllib3-2.2.3.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "urllib3==2.2.3", - "sha256": "e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", - "urls": [ - "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz" - ] - } - }, - "rules_python_publish_deps_311_zipp_py3_none_any_a817ac80": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "filename": "zipp-3.20.2-py3-none-any.whl", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "zipp==3.20.2", - "sha256": "a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", - "urls": [ - "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl" - ] - } - }, - "rules_python_publish_deps_311_zipp_sdist_bc9eb26f": { - "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", - "attributes": { - "dep_template": "@rules_python_publish_deps//{name}:{target}", - "experimental_target_platforms": [ - "cp311_linux_aarch64", - "cp311_linux_arm", - "cp311_linux_ppc", - "cp311_linux_s390x", - "cp311_linux_x86_64", - "cp311_osx_aarch64", - "cp311_osx_x86_64", - "cp311_windows_x86_64" - ], - "extra_pip_args": [ - "--index-url", - "https://pypi.org/simple" - ], - "filename": "zipp-3.20.2.tar.gz", - "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", - "repo": "rules_python_publish_deps_311", - "requirement": "zipp==3.20.2", - "sha256": "bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", - "urls": [ - "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz" - ] - } - }, - "pip_deps": { - "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", - "attributes": { - "repo_name": "pip_deps", - "extra_hub_aliases": {}, - "whl_map": { - "numpy": "{\"pip_deps_310_numpy\":[{\"version\":\"3.10\"}],\"pip_deps_311_numpy\":[{\"version\":\"3.11\"}],\"pip_deps_312_numpy\":[{\"version\":\"3.12\"}],\"pip_deps_38_numpy\":[{\"version\":\"3.8\"}],\"pip_deps_39_numpy\":[{\"version\":\"3.9\"}]}", - "setuptools": "{\"pip_deps_310_setuptools\":[{\"version\":\"3.10\"}],\"pip_deps_311_setuptools\":[{\"version\":\"3.11\"}],\"pip_deps_312_setuptools\":[{\"version\":\"3.12\"}],\"pip_deps_38_setuptools\":[{\"version\":\"3.8\"}],\"pip_deps_39_setuptools\":[{\"version\":\"3.9\"}]}" - }, - "packages": [ - "numpy", - "setuptools" - ], - "groups": {} - } - }, - "rules_fuzzing_py_deps": { - "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", - "attributes": { - "repo_name": "rules_fuzzing_py_deps", - "extra_hub_aliases": {}, - "whl_map": { - "absl_py": "{\"rules_fuzzing_py_deps_310_absl_py\":[{\"version\":\"3.10\"}],\"rules_fuzzing_py_deps_311_absl_py\":[{\"version\":\"3.11\"}],\"rules_fuzzing_py_deps_312_absl_py\":[{\"version\":\"3.12\"}],\"rules_fuzzing_py_deps_38_absl_py\":[{\"version\":\"3.8\"}],\"rules_fuzzing_py_deps_39_absl_py\":[{\"version\":\"3.9\"}]}", - "six": "{\"rules_fuzzing_py_deps_310_six\":[{\"version\":\"3.10\"}],\"rules_fuzzing_py_deps_311_six\":[{\"version\":\"3.11\"}],\"rules_fuzzing_py_deps_312_six\":[{\"version\":\"3.12\"}],\"rules_fuzzing_py_deps_38_six\":[{\"version\":\"3.8\"}],\"rules_fuzzing_py_deps_39_six\":[{\"version\":\"3.9\"}]}" - }, - "packages": [ - "absl_py", - "six" - ], - "groups": {} - } - }, - "rules_python_publish_deps": { - "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", - "attributes": { - "repo_name": "rules_python_publish_deps", - "extra_hub_aliases": {}, - "whl_map": { - "backports_tarfile": "{\"rules_python_publish_deps_311_backports_tarfile_py3_none_any_77e284d7\":[{\"filename\":\"backports.tarfile-1.2.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_backports_tarfile_sdist_d75e02c2\":[{\"filename\":\"backports_tarfile-1.2.0.tar.gz\",\"version\":\"3.11\"}]}", - "certifi": "{\"rules_python_publish_deps_311_certifi_py3_none_any_922820b5\":[{\"filename\":\"certifi-2024.8.30-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_certifi_sdist_bec941d2\":[{\"filename\":\"certifi-2024.8.30.tar.gz\",\"version\":\"3.11\"}]}", - "cffi": "{\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_a1ed2dd2\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_ppc64le_46bf4316\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_s390x_a24ed04c\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_x86_64_610faea7\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_aarch64_a9b15d49\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_x86_64_fc48c783\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_sdist_1c39c601\":[{\"filename\":\"cffi-1.17.1.tar.gz\",\"version\":\"3.11\"}]}", - "charset_normalizer": "{\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_universal2_0d99dd8f\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_x86_64_c57516e5\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_11_0_arm64_6dba5d19\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_aarch64_bf4475b8\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_ppc64le_ce031db0\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_s390x_8ff4e7cd\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_x86_64_3710a975\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_aarch64_47334db7\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_ppc64le_f1a2f519\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_s390x_63bc5c4a\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_x86_64_bcb4f8ea\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_win_amd64_cee4373f\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_py3_none_any_fe9f97fe\":[{\"filename\":\"charset_normalizer-3.4.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_sdist_223217c3\":[{\"filename\":\"charset_normalizer-3.4.0.tar.gz\",\"version\":\"3.11\"}]}", - "cryptography": "{\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_aarch64_846da004\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_x86_64_0f996e72\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_aarch64_f7b178f1\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_x86_64_c2e6fc39\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_aarch64_e1be4655\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_x86_64_df6b6c6d\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_sdist_315b9001\":[{\"filename\":\"cryptography-43.0.3.tar.gz\",\"version\":\"3.11\"}]}", - "docutils": "{\"rules_python_publish_deps_311_docutils_py3_none_any_dafca5b9\":[{\"filename\":\"docutils-0.21.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_docutils_sdist_3a6b1873\":[{\"filename\":\"docutils-0.21.2.tar.gz\",\"version\":\"3.11\"}]}", - "idna": "{\"rules_python_publish_deps_311_idna_py3_none_any_946d195a\":[{\"filename\":\"idna-3.10-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_idna_sdist_12f65c9b\":[{\"filename\":\"idna-3.10.tar.gz\",\"version\":\"3.11\"}]}", - "importlib_metadata": "{\"rules_python_publish_deps_311_importlib_metadata_py3_none_any_45e54197\":[{\"filename\":\"importlib_metadata-8.5.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_importlib_metadata_sdist_71522656\":[{\"filename\":\"importlib_metadata-8.5.0.tar.gz\",\"version\":\"3.11\"}]}", - "jaraco_classes": "{\"rules_python_publish_deps_311_jaraco_classes_py3_none_any_f662826b\":[{\"filename\":\"jaraco.classes-3.4.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_classes_sdist_47a024b5\":[{\"filename\":\"jaraco.classes-3.4.0.tar.gz\",\"version\":\"3.11\"}]}", - "jaraco_context": "{\"rules_python_publish_deps_311_jaraco_context_py3_none_any_f797fc48\":[{\"filename\":\"jaraco.context-6.0.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_context_sdist_9bae4ea5\":[{\"filename\":\"jaraco_context-6.0.1.tar.gz\",\"version\":\"3.11\"}]}", - "jaraco_functools": "{\"rules_python_publish_deps_311_jaraco_functools_py3_none_any_ad159f13\":[{\"filename\":\"jaraco.functools-4.1.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_functools_sdist_70f7e0e2\":[{\"filename\":\"jaraco_functools-4.1.0.tar.gz\",\"version\":\"3.11\"}]}", - "jeepney": "{\"rules_python_publish_deps_311_jeepney_py3_none_any_c0a454ad\":[{\"filename\":\"jeepney-0.8.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jeepney_sdist_5efe48d2\":[{\"filename\":\"jeepney-0.8.0.tar.gz\",\"version\":\"3.11\"}]}", - "keyring": "{\"rules_python_publish_deps_311_keyring_py3_none_any_5426f817\":[{\"filename\":\"keyring-25.4.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_keyring_sdist_b07ebc55\":[{\"filename\":\"keyring-25.4.1.tar.gz\",\"version\":\"3.11\"}]}", - "markdown_it_py": "{\"rules_python_publish_deps_311_markdown_it_py_py3_none_any_35521684\":[{\"filename\":\"markdown_it_py-3.0.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_markdown_it_py_sdist_e3f60a94\":[{\"filename\":\"markdown-it-py-3.0.0.tar.gz\",\"version\":\"3.11\"}]}", - "mdurl": "{\"rules_python_publish_deps_311_mdurl_py3_none_any_84008a41\":[{\"filename\":\"mdurl-0.1.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_mdurl_sdist_bb413d29\":[{\"filename\":\"mdurl-0.1.2.tar.gz\",\"version\":\"3.11\"}]}", - "more_itertools": "{\"rules_python_publish_deps_311_more_itertools_py3_none_any_037b0d32\":[{\"filename\":\"more_itertools-10.5.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_more_itertools_sdist_5482bfef\":[{\"filename\":\"more-itertools-10.5.0.tar.gz\",\"version\":\"3.11\"}]}", - "nh3": "{\"rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_14c5a72e\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_7b7c2a3c\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_aarch64_42c64511\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_armv7l_0411beb0\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64_5f36b271\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64le_34c03fa7\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_s390x_19aaba96\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_x86_64_de3ceed6\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_aarch64_f0eca9ca\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_armv7l_3a157ab1\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_x86_64_36c95d4b\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_win_amd64_8ce0f819\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-win_amd64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_sdist_94a16692\":[{\"filename\":\"nh3-0.2.18.tar.gz\",\"version\":\"3.11\"}]}", - "pkginfo": "{\"rules_python_publish_deps_311_pkginfo_py3_none_any_889a6da2\":[{\"filename\":\"pkginfo-1.10.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pkginfo_sdist_5df73835\":[{\"filename\":\"pkginfo-1.10.0.tar.gz\",\"version\":\"3.11\"}]}", - "pycparser": "{\"rules_python_publish_deps_311_pycparser_py3_none_any_c3702b6d\":[{\"filename\":\"pycparser-2.22-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pycparser_sdist_491c8be9\":[{\"filename\":\"pycparser-2.22.tar.gz\",\"version\":\"3.11\"}]}", - "pygments": "{\"rules_python_publish_deps_311_pygments_py3_none_any_b8e6aca0\":[{\"filename\":\"pygments-2.18.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pygments_sdist_786ff802\":[{\"filename\":\"pygments-2.18.0.tar.gz\",\"version\":\"3.11\"}]}", - "pywin32_ctypes": "{\"rules_python_publish_deps_311_pywin32_ctypes_py3_none_any_8a151337\":[{\"filename\":\"pywin32_ctypes-0.2.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pywin32_ctypes_sdist_d162dc04\":[{\"filename\":\"pywin32-ctypes-0.2.3.tar.gz\",\"version\":\"3.11\"}]}", - "readme_renderer": "{\"rules_python_publish_deps_311_readme_renderer_py3_none_any_2fbca89b\":[{\"filename\":\"readme_renderer-44.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_readme_renderer_sdist_8712034e\":[{\"filename\":\"readme_renderer-44.0.tar.gz\",\"version\":\"3.11\"}]}", - "requests": "{\"rules_python_publish_deps_311_requests_py3_none_any_70761cfe\":[{\"filename\":\"requests-2.32.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_requests_sdist_55365417\":[{\"filename\":\"requests-2.32.3.tar.gz\",\"version\":\"3.11\"}]}", - "requests_toolbelt": "{\"rules_python_publish_deps_311_requests_toolbelt_py2_none_any_cccfdd66\":[{\"filename\":\"requests_toolbelt-1.0.0-py2.py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_requests_toolbelt_sdist_7681a0a3\":[{\"filename\":\"requests-toolbelt-1.0.0.tar.gz\",\"version\":\"3.11\"}]}", - "rfc3986": "{\"rules_python_publish_deps_311_rfc3986_py2_none_any_50b1502b\":[{\"filename\":\"rfc3986-2.0.0-py2.py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_rfc3986_sdist_97aacf9d\":[{\"filename\":\"rfc3986-2.0.0.tar.gz\",\"version\":\"3.11\"}]}", - "rich": "{\"rules_python_publish_deps_311_rich_py3_none_any_9836f509\":[{\"filename\":\"rich-13.9.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_rich_sdist_bc1e01b8\":[{\"filename\":\"rich-13.9.3.tar.gz\",\"version\":\"3.11\"}]}", - "secretstorage": "{\"rules_python_publish_deps_311_secretstorage_py3_none_any_f356e662\":[{\"filename\":\"SecretStorage-3.3.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_secretstorage_sdist_2403533e\":[{\"filename\":\"SecretStorage-3.3.3.tar.gz\",\"version\":\"3.11\"}]}", - "twine": "{\"rules_python_publish_deps_311_twine_py3_none_any_215dbe7b\":[{\"filename\":\"twine-5.1.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_twine_sdist_9aa08251\":[{\"filename\":\"twine-5.1.1.tar.gz\",\"version\":\"3.11\"}]}", - "urllib3": "{\"rules_python_publish_deps_311_urllib3_py3_none_any_ca899ca0\":[{\"filename\":\"urllib3-2.2.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_urllib3_sdist_e7d814a8\":[{\"filename\":\"urllib3-2.2.3.tar.gz\",\"version\":\"3.11\"}]}", - "zipp": "{\"rules_python_publish_deps_311_zipp_py3_none_any_a817ac80\":[{\"filename\":\"zipp-3.20.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_zipp_sdist_bc9eb26f\":[{\"filename\":\"zipp-3.20.2.tar.gz\",\"version\":\"3.11\"}]}" - }, - "packages": [ - "backports_tarfile", - "certifi", - "charset_normalizer", - "docutils", - "idna", - "importlib_metadata", - "jaraco_classes", - "jaraco_context", - "jaraco_functools", - "keyring", - "markdown_it_py", - "mdurl", - "more_itertools", - "nh3", - "pkginfo", - "pygments", - "readme_renderer", - "requests", - "requests_toolbelt", - "rfc3986", - "rich", - "twine", - "urllib3", - "zipp" - ], - "groups": {} - } - } - }, - "recordedRepoMappingEntries": [ - [ - "bazel_features+", - "bazel_features_globals", - "bazel_features++version_extension+bazel_features_globals" - ], - [ - "bazel_features+", - "bazel_features_version", - "bazel_features++version_extension+bazel_features_version" - ], - [ - "rules_python+", - "bazel_features", - "bazel_features+" - ], - [ - "rules_python+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "rules_python+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_python+", - "pypi__build", - "rules_python++internal_deps+pypi__build" - ], - [ - "rules_python+", - "pypi__click", - "rules_python++internal_deps+pypi__click" - ], - [ - "rules_python+", - "pypi__colorama", - "rules_python++internal_deps+pypi__colorama" - ], - [ - "rules_python+", - "pypi__importlib_metadata", - "rules_python++internal_deps+pypi__importlib_metadata" - ], - [ - "rules_python+", - "pypi__installer", - "rules_python++internal_deps+pypi__installer" - ], - [ - "rules_python+", - "pypi__more_itertools", - "rules_python++internal_deps+pypi__more_itertools" - ], - [ - "rules_python+", - "pypi__packaging", - "rules_python++internal_deps+pypi__packaging" - ], - [ - "rules_python+", - "pypi__pep517", - "rules_python++internal_deps+pypi__pep517" - ], - [ - "rules_python+", - "pypi__pip", - "rules_python++internal_deps+pypi__pip" - ], - [ - "rules_python+", - "pypi__pip_tools", - "rules_python++internal_deps+pypi__pip_tools" - ], - [ - "rules_python+", - "pypi__pyproject_hooks", - "rules_python++internal_deps+pypi__pyproject_hooks" - ], - [ - "rules_python+", - "pypi__setuptools", - "rules_python++internal_deps+pypi__setuptools" - ], - [ - "rules_python+", - "pypi__tomli", - "rules_python++internal_deps+pypi__tomli" - ], - [ - "rules_python+", - "pypi__wheel", - "rules_python++internal_deps+pypi__wheel" - ], - [ - "rules_python+", - "pypi__zipp", - "rules_python++internal_deps+pypi__zipp" - ], - [ - "rules_python+", - "pythons_hub", - "rules_python++python+pythons_hub" - ], - [ - "rules_python++python+pythons_hub", - "python_3_10_host", - "rules_python++python+python_3_10_host" - ], - [ - "rules_python++python+pythons_hub", - "python_3_11_host", - "rules_python++python+python_3_11_host" - ], - [ - "rules_python++python+pythons_hub", - "python_3_12_host", - "rules_python++python+python_3_12_host" - ], - [ - "rules_python++python+pythons_hub", - "python_3_8_host", - "rules_python++python+python_3_8_host" - ], - [ - "rules_python++python+pythons_hub", - "python_3_9_host", - "rules_python++python+python_3_9_host" - ] - ] - } - }, - "@@rules_python+//python/uv:uv.bzl%uv": { - "general": { - "bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=", - "usagesDigest": "4JapxcpS0mL3524k0TZJffAtVyuRjDHZvN9kBRxxF1U=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "uv": { - "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", - "attributes": { - "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", - "toolchain_names": [ - "none" - ], - "toolchain_implementations": { - "none": "'@@rules_python+//python:none'" - }, - "toolchain_compatible_with": { - "none": [ - "@platforms//:incompatible" - ] - }, - "toolchain_target_settings": {} - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python+", - "platforms", - "platforms" - ] - ] - } - }, - "@@yq.bzl+//yq:extensions.bzl%yq": { - "general": { - "bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=", - "usagesDigest": "z7g7IZt6aaEeYztkX2Hs5qlSifRJcDTGz9K/CD4yOFI=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "yq_darwin_amd64": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "darwin_amd64", - "version": "4.45.2" - } - }, - "yq_darwin_arm64": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "darwin_arm64", - "version": "4.45.2" - } - }, - "yq_linux_amd64": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "linux_amd64", - "version": "4.45.2" - } - }, - "yq_linux_arm64": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "linux_arm64", - "version": "4.45.2" - } - }, - "yq_linux_s390x": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "linux_s390x", - "version": "4.45.2" - } - }, - "yq_linux_riscv64": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "linux_riscv64", - "version": "4.45.2" - } - }, - "yq_linux_ppc64le": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "linux_ppc64le", - "version": "4.45.2" - } - }, - "yq_windows_amd64": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "windows_amd64", - "version": "4.45.2" - } - }, - "yq_windows_arm64": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", - "attributes": { - "platform": "windows_arm64", - "version": "4.45.2" - } - }, - "yq_toolchains": { - "repoRuleId": "@@yq.bzl+//yq/toolchain:toolchain.bzl%yq_toolchains_repo", - "attributes": { - "user_repository_name": "yq" - } - } - }, - "recordedRepoMappingEntries": [] - } - } - }, - "facts": { - "@@rules_go+//go:extensions.bzl%go_sdk": { - "1.25.0": { - "aix_ppc64": [ - "go1.25.0.aix-ppc64.tar.gz", - "e5234a7dac67bc86c528fe9752fc9d63557918627707a733ab4cac1a6faed2d4" - ], - "darwin_amd64": [ - "go1.25.0.darwin-amd64.tar.gz", - "5bd60e823037062c2307c71e8111809865116714d6f6b410597cf5075dfd80ef" - ], - "darwin_arm64": [ - "go1.25.0.darwin-arm64.tar.gz", - "544932844156d8172f7a28f77f2ac9c15a23046698b6243f633b0a0b00c0749c" - ], - "dragonfly_amd64": [ - "go1.25.0.dragonfly-amd64.tar.gz", - "5ed3cf9a810a1483822538674f1336c06b51aa1b94d6d545a1a0319a48177120" - ], - "freebsd_386": [ - "go1.25.0.freebsd-386.tar.gz", - "abea5d5c6697e6b5c224731f2158fe87c602996a2a233ac0c4730cd57bf8374e" - ], - "freebsd_amd64": [ - "go1.25.0.freebsd-amd64.tar.gz", - "86e6fe0a29698d7601c4442052dac48bd58d532c51cccb8f1917df648138730b" - ], - "freebsd_arm": [ - "go1.25.0.freebsd-arm.tar.gz", - "d90b78e41921f72f30e8bbc81d9dec2cff7ff384a33d8d8debb24053e4336bfe" - ], - "freebsd_arm64": [ - "go1.25.0.freebsd-arm64.tar.gz", - "451d0da1affd886bfb291b7c63a6018527b269505db21ce6e14724f22ab0662e" - ], - "freebsd_riscv64": [ - "go1.25.0.freebsd-riscv64.tar.gz", - "7b565f76bd8bda46549eeaaefe0e53b251e644c230577290c0f66b1ecdb3cdbe" - ], - "illumos_amd64": [ - "go1.25.0.illumos-amd64.tar.gz", - "b1e1fdaab1ad25aa1c08d7a36c97d45d74b98b89c3f78c6d2145f77face54a2c" - ], - "linux_386": [ - "go1.25.0.linux-386.tar.gz", - "8c602dd9d99bc9453b3995d20ce4baf382cc50855900a0ece5de9929df4a993a" - ], - "linux_amd64": [ - "go1.25.0.linux-amd64.tar.gz", - "2852af0cb20a13139b3448992e69b868e50ed0f8a1e5940ee1de9e19a123b613" - ], - "linux_arm64": [ - "go1.25.0.linux-arm64.tar.gz", - "05de75d6994a2783699815ee553bd5a9327d8b79991de36e38b66862782f54ae" - ], - "linux_armv6l": [ - "go1.25.0.linux-armv6l.tar.gz", - "a5a8f8198fcf00e1e485b8ecef9ee020778bf32a408a4e8873371bfce458cd09" - ], - "linux_loong64": [ - "go1.25.0.linux-loong64.tar.gz", - "cab86b1cf761b1cb3bac86a8877cfc92e7b036fc0d3084123d77013d61432afc" - ], - "linux_mips": [ - "go1.25.0.linux-mips.tar.gz", - "d66b6fb74c3d91b9829dc95ec10ca1f047ef5e89332152f92e136cf0e2da5be1" - ], - "linux_mips64": [ - "go1.25.0.linux-mips64.tar.gz", - "4082e4381a8661bc2a839ff94ba3daf4f6cde20f8fb771b5b3d4762dc84198a2" - ], - "linux_mips64le": [ - "go1.25.0.linux-mips64le.tar.gz", - "70002c299ec7f7175ac2ef673b1b347eecfa54ae11f34416a6053c17f855afcc" - ], - "linux_mipsle": [ - "go1.25.0.linux-mipsle.tar.gz", - "b00a3a39eff099f6df9f1c7355bf28e4589d0586f42d7d4a394efb763d145a73" - ], - "linux_ppc64": [ - "go1.25.0.linux-ppc64.tar.gz", - "df166f33bd98160662560a72ff0b4ba731f969a80f088922bddcf566a88c1ec1" - ], - "linux_ppc64le": [ - "go1.25.0.linux-ppc64le.tar.gz", - "0f18a89e7576cf2c5fa0b487a1635d9bcbf843df5f110e9982c64df52a983ad0" - ], - "linux_riscv64": [ - "go1.25.0.linux-riscv64.tar.gz", - "c018ff74a2c48d55c8ca9b07c8e24163558ffec8bea08b326d6336905d956b67" - ], - "linux_s390x": [ - "go1.25.0.linux-s390x.tar.gz", - "34e5a2e19f2292fbaf8783e3a241e6e49689276aef6510a8060ea5ef54eee408" - ], - "netbsd_386": [ - "go1.25.0.netbsd-386.tar.gz", - "f8586cdb7aa855657609a5c5f6dbf523efa00c2bbd7c76d3936bec80aa6c0aba" - ], - "netbsd_amd64": [ - "go1.25.0.netbsd-amd64.tar.gz", - "ae8dc1469385b86a157a423bb56304ba45730de8a897615874f57dd096db2c2a" - ], - "netbsd_arm": [ - "go1.25.0.netbsd-arm.tar.gz", - "1ff7e4cc764425fc9dd6825eaee79d02b3c7cafffbb3691687c8d672ade76cb7" - ], - "netbsd_arm64": [ - "go1.25.0.netbsd-arm64.tar.gz", - "e1b310739f26724216aa6d7d7208c4031f9ff54c9b5b9a796ddc8bebcb4a5f16" - ], - "openbsd_386": [ - "go1.25.0.openbsd-386.tar.gz", - "4802a9b20e533da91adb84aab42e94aa56cfe3e5475d0550bed3385b182e69d8" - ], - "openbsd_amd64": [ - "go1.25.0.openbsd-amd64.tar.gz", - "c016cd984bebe317b19a4f297c4f50def120dc9788490540c89f28e42f1dabe1" - ], - "openbsd_arm": [ - "go1.25.0.openbsd-arm.tar.gz", - "a1e31d0bf22172ddde42edf5ec811ef81be43433df0948ece52fecb247ccfd8d" - ], - "openbsd_arm64": [ - "go1.25.0.openbsd-arm64.tar.gz", - "343ea8edd8c218196e15a859c6072d0dd3246fbbb168481ab665eb4c4140458d" - ], - "openbsd_ppc64": [ - "go1.25.0.openbsd-ppc64.tar.gz", - "694c14da1bcaeb5e3332d49bdc2b6d155067648f8fe1540c5de8f3cf8e157154" - ], - "openbsd_riscv64": [ - "go1.25.0.openbsd-riscv64.tar.gz", - "aa510ad25cf54c06cd9c70b6d80ded69cb20188ac6e1735655eef29ff7e7885f" - ], - "plan9_386": [ - "go1.25.0.plan9-386.tar.gz", - "46f8cef02086cf04bf186c5912776b56535178d4cb319cd19c9fdbdd29231986" - ], - "plan9_amd64": [ - "go1.25.0.plan9-amd64.tar.gz", - "29b34391d84095e44608a228f63f2f88113a37b74a79781353ec043dfbcb427b" - ], - "plan9_arm": [ - "go1.25.0.plan9-arm.tar.gz", - "0a047107d13ebe7943aaa6d54b1d7bbd2e45e68ce449b52915a818da715799c2" - ], - "solaris_amd64": [ - "go1.25.0.solaris-amd64.tar.gz", - "9977f9e4351984364a3b2b78f8b88bfd1d339812356d5237678514594b7d3611" - ], - "windows_386": [ - "go1.25.0.windows-386.zip", - "df9f39db82a803af0db639e3613a36681ab7a42866b1384b3f3a1045663961a7" - ], - "windows_amd64": [ - "go1.25.0.windows-amd64.zip", - "89efb4f9b30812eee083cc1770fdd2913c14d301064f6454851428f9707d190b" - ], - "windows_arm64": [ - "go1.25.0.windows-arm64.zip", - "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" - ] - }, - "1.26.2": { - "aix_ppc64": [ - "go1.26.2.aix-ppc64.tar.gz", - "e6f759fbdd5b1e3ecce3161d8bd9b9aeaf15c4112b7c101097e9d329b310d858" - ], - "darwin_amd64": [ - "go1.26.2.darwin-amd64.tar.gz", - "bc3f1500d9968c36d705442d90ba91addf9271665033748b82532682e90a7966" - ], - "darwin_arm64": [ - "go1.26.2.darwin-arm64.tar.gz", - "32af1522bf3e3ff3975864780a429cc0b41d190ec7bf90faa661d6d64566e7af" - ], - "dragonfly_amd64": [ - "go1.26.2.dragonfly-amd64.tar.gz", - "b4f3f74412914e54140cbf8b5c76d2f8eeff3a5003310c9244c61ba8a0ecd94b" - ], - "freebsd_386": [ - "go1.26.2.freebsd-386.tar.gz", - "fab09ea1988aae3ae2c8186455e8956d539d04e2ee4973e8887853432d0e8039" - ], - "freebsd_amd64": [ - "go1.26.2.freebsd-amd64.tar.gz", - "f271fd829a2a6b36fa1c72cdaafb18410a106da982c93a626d4e8b0fa0f0fa21" - ], - "freebsd_arm": [ - "go1.26.2.freebsd-arm.tar.gz", - "38713f1516cd2b0097ea05594ec1c842fe690dace8301c7d34d91b92250b4424" - ], - "freebsd_arm64": [ - "go1.26.2.freebsd-arm64.tar.gz", - "d78bb171900134efdd1d0d49e5e80cd8c8b614f0e46c508d0b6bac30fb996fdf" - ], - "illumos_amd64": [ - "go1.26.2.illumos-amd64.tar.gz", - "e88cd85e9e253ceda4077367072aa10d2f92bc2fa6e59a811c00bbe95dc9b02d" - ], - "linux_386": [ - "go1.26.2.linux-386.tar.gz", - "89835cdc4dfebde7fe28c9c6dc080bb3753f6b0354301966ff9f62d14991bd7d" - ], - "linux_amd64": [ - "go1.26.2.linux-amd64.tar.gz", - "990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282" - ], - "linux_arm64": [ - "go1.26.2.linux-arm64.tar.gz", - "c958a1fe1b361391db163a485e21f5f228142d6f8b584f6bef89b26f66dc5b23" - ], - "linux_armv6l": [ - "go1.26.2.linux-armv6l.tar.gz", - "0000e45577827b0a8868588c543cbe4232853def1d3d7a344ad6e60ce2b015c8" - ], - "linux_loong64": [ - "go1.26.2.linux-loong64.tar.gz", - "4dcb87e845fe5c015c8cf6affb4636fcf1699182b70454783caed85c5dfa3267" - ], - "linux_mips": [ - "go1.26.2.linux-mips.tar.gz", - "2d57e4167932a4872e31570465f48d8b6818002c77275bae969bdbb6d7238b5e" - ], - "linux_mips64": [ - "go1.26.2.linux-mips64.tar.gz", - "b0b49bb5c528e623a926b8242f03cfd612971150e18eeb3f638d751218c09cdf" - ], - "linux_mips64le": [ - "go1.26.2.linux-mips64le.tar.gz", - "5ffc9da2b0ee939c8503c9d9278d6857909ca8577dae3b71221ab2821dc7826a" - ], - "linux_mipsle": [ - "go1.26.2.linux-mipsle.tar.gz", - "ab1fe1c38ffa6bbda029dea33bf36167aca4ff3a25c9d6ed0af38da120678ddc" - ], - "linux_ppc64": [ - "go1.26.2.linux-ppc64.tar.gz", - "589f7ef241104f153e910244b71d70f4aad0d4584651ca80a5188186dda63a2e" - ], - "linux_ppc64le": [ - "go1.26.2.linux-ppc64le.tar.gz", - "62b7645dd2404052535617c59e91cf03c7aa28e332dbaddbe4c0d7de7bcc6736" - ], - "linux_riscv64": [ - "go1.26.2.linux-riscv64.tar.gz", - "c5c697faa4dc05364b6e163d2ab8161b32a120eeed54192457d57d7ef7c2091a" - ], - "linux_s390x": [ - "go1.26.2.linux-s390x.tar.gz", - "410726ed10a0ea6745c2ea8da4f0e769fd3ce819cd4a41a67ad08b094d5dfc31" - ], - "netbsd_386": [ - "go1.26.2.netbsd-386.tar.gz", - "e8ffab99dd65fef14097d6af48ea6302793f2298a7b2e5f00a284bd933feba4c" - ], - "netbsd_amd64": [ - "go1.26.2.netbsd-amd64.tar.gz", - "1f5c33c923983ee8433ad8098dcd87a0e1fdccd18d05a91844c2be60507a61fe" - ], - "netbsd_arm": [ - "go1.26.2.netbsd-arm.tar.gz", - "85761320e364b65424d2952a3388970d86e072c8dce8dcad2a1dca41555c2b96" - ], - "netbsd_arm64": [ - "go1.26.2.netbsd-arm64.tar.gz", - "3ca3561bf4452e799d11e5312182f79cd342d506136cd44c2b47991206ec23f5" - ], - "openbsd_386": [ - "go1.26.2.openbsd-386.tar.gz", - "0644073c0ae1ade26d26953ef882d7d419855dca25b0e992ae416a47967d37b3" - ], - "openbsd_amd64": [ - "go1.26.2.openbsd-amd64.tar.gz", - "72f69217a88e3d0975a75adf9fc92ff10ea65def56e6c34d8612428bf769581e" - ], - "openbsd_arm": [ - "go1.26.2.openbsd-arm.tar.gz", - "3aafe792df65abf1777b5cc678075ebba1bd8063e6450e81e742fef696e0bcbd" - ], - "openbsd_arm64": [ - "go1.26.2.openbsd-arm64.tar.gz", - "efd410fc60a17690ad43fe8dd00bf1fd4c1dc920d81970b9f422f313b0930b92" - ], - "openbsd_ppc64": [ - "go1.26.2.openbsd-ppc64.tar.gz", - "98a2cadd416066739e00b1bc297727c3108dba9001811b177ce57afef518f8bd" - ], - "openbsd_riscv64": [ - "go1.26.2.openbsd-riscv64.tar.gz", - "adf39a1a7e56d5c1a2cb69ad06752c5da6b808d2a029b7b6d6d5bb6e11a2168e" - ], - "plan9_386": [ - "go1.26.2.plan9-386.tar.gz", - "eb2f95f6f43701eb98a31f5efdd9b5f14ff6e0afd289e1f94559462f83e73cfd" - ], - "plan9_amd64": [ - "go1.26.2.plan9-amd64.tar.gz", - "d347fecec7532309fccf3570021ba8a00a0acce120fea02a46cc295936588b0f" - ], - "plan9_arm": [ - "go1.26.2.plan9-arm.tar.gz", - "70700c5af45201a8e82436fb824f3551e357e3002094c8416e78e1aa51d4a0ab" - ], - "solaris_amd64": [ - "go1.26.2.solaris-amd64.tar.gz", - "cd45d13200697b3263e39cdf364f0cb9d9adc39d9574ab575e481b64cb7fb8b1" - ], - "windows_386": [ - "go1.26.2.windows-386.zip", - "4a8b02c34625fecd9c6583442101c9796fc265a5fc1edb9340d71bed0300f94c" - ], - "windows_amd64": [ - "go1.26.2.windows-amd64.zip", - "98eb3570bade15cb826b0909338df6cc6d2cf590bc39c471142002db3832b708" - ], - "windows_arm64": [ - "go1.26.2.windows-arm64.zip", - "094d05caaf6ba235e2bd570b625d064ceb65943866252722a8f3fdba232139c6" - ] - } - } - } -} diff --git a/src/uis/nvcf-ui/backend/BUILD.bazel b/src/uis/nvcf-ui/backend/BUILD.bazel index e089f5e99..b72fc9126 100644 --- a/src/uis/nvcf-ui/backend/BUILD.bazel +++ b/src/uis/nvcf-ui/backend/BUILD.bazel @@ -22,15 +22,19 @@ filegroup( name = "sources", srcs = glob( - ["*.go", "go.mod", "go.sum"], + [ + "*.go", + "go.mod", + "go.sum", + ], allow_empty = True, ) + [ - "//backend/cmd/control-plane:sources", - "//backend/cmd/server:sources", - "//backend/internal/control-plane:sources", - "//backend/internal/middleware:sources", - "//backend/internal/token-watcher:sources", - "//backend/internal/utils:sources", + "//src/uis/nvcf-ui/backend/cmd/control-plane:sources", + "//src/uis/nvcf-ui/backend/cmd/server:sources", + "//src/uis/nvcf-ui/backend/internal/control-plane:sources", + "//src/uis/nvcf-ui/backend/internal/middleware:sources", + "//src/uis/nvcf-ui/backend/internal/token-watcher:sources", + "//src/uis/nvcf-ui/backend/internal/utils:sources", ], visibility = ["//visibility:public"], ) diff --git a/src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel b/src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel index 66dc21f14..13f7f1e52 100644 --- a/src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel +++ b/src/uis/nvcf-ui/backend/cmd/control-plane/BUILD.bazel @@ -21,8 +21,8 @@ go_library( importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/cmd/control-plane", visibility = ["//visibility:private"], deps = [ - "//backend/internal/control-plane", - "//backend/internal/utils", + "//src/uis/nvcf-ui/backend/internal/control-plane", + "//src/uis/nvcf-ui/backend/internal/utils", "@io_k8s_sigs_controller_runtime//pkg/manager/signals", ], ) @@ -33,9 +33,14 @@ go_binary( visibility = ["//visibility:public"], ) - filegroup( name = "sources", - srcs = glob(["*.go", "*.md"], allow_empty = True), + srcs = glob( + [ + "*.go", + "*.md", + ], + allow_empty = True, + ), visibility = ["//visibility:public"], ) diff --git a/src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel b/src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel index 07619c734..9db5f06c9 100644 --- a/src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel +++ b/src/uis/nvcf-ui/backend/cmd/server/BUILD.bazel @@ -21,10 +21,10 @@ go_library( importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/cmd/server", visibility = ["//visibility:private"], deps = [ - "//backend/internal/control-plane", - "//backend/internal/middleware", - "//backend/internal/token-watcher", - "//backend/internal/utils", + "//src/uis/nvcf-ui/backend/internal/control-plane", + "//src/uis/nvcf-ui/backend/internal/middleware", + "//src/uis/nvcf-ui/backend/internal/token-watcher", + "//src/uis/nvcf-ui/backend/internal/utils", "@com_github_rs_zerolog//:zerolog", "@com_github_rs_zerolog//hlog", "@io_k8s_sigs_controller_runtime//pkg/manager/signals", @@ -37,9 +37,14 @@ go_binary( visibility = ["//visibility:public"], ) - filegroup( name = "sources", - srcs = glob(["*.go", "*.md"], allow_empty = True), + srcs = glob( + [ + "*.go", + "*.md", + ], + allow_empty = True, + ), visibility = ["//visibility:public"], ) diff --git a/src/uis/nvcf-ui/backend/go.mod b/src/uis/nvcf-ui/backend/go.mod index 9c52fd6e7..bac0b42f5 100644 --- a/src/uis/nvcf-ui/backend/go.mod +++ b/src/uis/nvcf-ui/backend/go.mod @@ -9,7 +9,7 @@ require ( k8s.io/api v0.36.0 k8s.io/apimachinery v0.36.0 k8s.io/client-go v0.36.0 - sigs.k8s.io/controller-runtime v0.24.1 + sigs.k8s.io/controller-runtime v0.22.5 ) require ( @@ -23,6 +23,7 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/src/uis/nvcf-ui/backend/go.sum b/src/uis/nvcf-ui/backend/go.sum index b8ca5c167..cc5b26f89 100644 --- a/src/uis/nvcf-ui/backend/go.sum +++ b/src/uis/nvcf-ui/backend/go.sum @@ -1,5 +1,3 @@ -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -33,6 +31,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -40,8 +40,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -73,10 +73,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= -github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= -github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -121,8 +121,6 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= @@ -168,8 +166,8 @@ k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hk k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= -sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= +sigs.k8s.io/controller-runtime v0.22.5 h1:v3nfSUMowX/2WMp27J9slwGFyAt7IV0YwBxAkrUr0GE= +sigs.k8s.io/controller-runtime v0.22.5/go.mod h1:pc5SoYWnWI6I+cBHYYdZ7B6YHZVY5xNfll88JB+vniI= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel index a1064d4aa..516685b8a 100644 --- a/src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel +++ b/src/uis/nvcf-ui/backend/internal/control-plane/BUILD.bazel @@ -25,9 +25,9 @@ go_library( "k8shealth.go", ], importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/control-plane", - visibility = ["//backend:__subpackages__"], + visibility = ["//src/uis/nvcf-ui/backend:__subpackages__"], deps = [ - "//backend/internal/utils", + "//src/uis/nvcf-ui/backend/internal/utils", "@com_github_rs_zerolog//:zerolog", "@in_yaml_go_yaml_v3//:yaml", "@io_k8s_api//apps/v1:apps", @@ -63,9 +63,14 @@ go_test( ], ) - filegroup( name = "sources", - srcs = glob(["*.go", "*.md"], allow_empty = True), + srcs = glob( + [ + "*.go", + "*.md", + ], + allow_empty = True, + ), visibility = ["//visibility:public"], ) diff --git a/src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel index 0505053b2..75fe90a7e 100644 --- a/src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel +++ b/src/uis/nvcf-ui/backend/internal/middleware/BUILD.bazel @@ -19,7 +19,7 @@ go_library( name = "middleware", srcs = ["middleware.go"], importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/middleware", - visibility = ["//backend:__subpackages__"], + visibility = ["//src/uis/nvcf-ui/backend:__subpackages__"], deps = [ "@com_github_rs_zerolog//:zerolog", "@com_github_rs_zerolog//hlog", @@ -36,9 +36,14 @@ go_test( ], ) - filegroup( name = "sources", - srcs = glob(["*.go", "*.md"], allow_empty = True), + srcs = glob( + [ + "*.go", + "*.md", + ], + allow_empty = True, + ), visibility = ["//visibility:public"], ) diff --git a/src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel index 7eb8bbbb6..6dc66fe60 100644 --- a/src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel +++ b/src/uis/nvcf-ui/backend/internal/token-watcher/BUILD.bazel @@ -19,9 +19,9 @@ go_library( name = "token-watcher", srcs = ["watcher.go"], importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/token-watcher", - visibility = ["//backend:__subpackages__"], + visibility = ["//src/uis/nvcf-ui/backend:__subpackages__"], deps = [ - "//backend/internal/utils", + "//src/uis/nvcf-ui/backend/internal/utils", "@com_github_fsnotify_fsnotify//:fsnotify", "@com_github_rs_zerolog//:zerolog", ], @@ -34,9 +34,14 @@ go_test( deps = ["@com_github_rs_zerolog//:zerolog"], ) - filegroup( name = "sources", - srcs = glob(["*.go", "*.md"], allow_empty = True), + srcs = glob( + [ + "*.go", + "*.md", + ], + allow_empty = True, + ), visibility = ["//visibility:public"], ) diff --git a/src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel b/src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel index 612c427ae..9e82c6697 100644 --- a/src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel +++ b/src/uis/nvcf-ui/backend/internal/utils/BUILD.bazel @@ -19,7 +19,7 @@ go_library( name = "utils", srcs = ["utils.go"], importpath = "github.com/NVIDIA/nvcf/src/control-plane-services/nvcf-ui/backend/internal/utils", - visibility = ["//backend:__subpackages__"], + visibility = ["//src/uis/nvcf-ui/backend:__subpackages__"], deps = [ "@com_github_rs_zerolog//:zerolog", "@io_k8s_apimachinery//pkg/runtime", @@ -36,9 +36,14 @@ go_test( deps = ["@com_github_rs_zerolog//:zerolog"], ) - filegroup( name = "sources", - srcs = glob(["*.go", "*.md"], allow_empty = True), + srcs = glob( + [ + "*.go", + "*.md", + ], + allow_empty = True, + ), visibility = ["//visibility:public"], ) diff --git a/src/uis/nvcf-ui/go.work.bazel b/src/uis/nvcf-ui/go.work.bazel deleted file mode 100644 index 4864ce4cf..000000000 --- a/src/uis/nvcf-ui/go.work.bazel +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Bazel-scope Go workspace consumed by gazelle's go_deps (via -// go_deps.from_file). Named .bazel so it does not affect the backend's tooling. -go 1.26.2 - -use ( - ./backend -) diff --git a/src/uis/nvcf-ui/platforms/BUILD.bazel b/src/uis/nvcf-ui/platforms/BUILD.bazel deleted file mode 100644 index 3d51e50f1..000000000 --- a/src/uis/nvcf-ui/platforms/BUILD.bazel +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "sources", - srcs = glob(["*.bazel"]), -) - -platform( - name = "linux_arm64", - constraint_values = [ - "@platforms//os:linux", - "@platforms//cpu:aarch64", - ], -) - -platform( - name = "linux_x86_64", - constraint_values = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], -) diff --git a/src/uis/nvcf-ui/rules/oci/BUILD.bazel b/src/uis/nvcf-ui/rules/oci/BUILD.bazel deleted file mode 100644 index 2096da0b1..000000000 --- a/src/uis/nvcf-ui/rules/oci/BUILD.bazel +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# OCI image helpers. -# A cacerts target can be added here later if using scratch/distroless bases. - -filegroup( - name = "sources", - srcs = glob(["*.bzl", "*.bazel"]) + ["//rules/oci/private:sources"], - visibility = ["//visibility:public"], -) diff --git a/src/uis/nvcf-ui/rules/oci/defs.bzl b/src/uis/nvcf-ui/rules/oci/defs.bzl deleted file mode 100644 index 92058608d..000000000 --- a/src/uis/nvcf-ui/rules/oci/defs.bzl +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"OCI image rules for packaging binaries into multi-arch containers." - -load("//rules/oci/private:go.bzl", _go_web_oci_image = "go_web_oci_image") - -# Packages Go binaries + a static web-asset directory into a multi-arch image. -go_web_oci_image = _go_web_oci_image diff --git a/src/uis/nvcf-ui/rules/oci/private/BUILD.bazel b/src/uis/nvcf-ui/rules/oci/private/BUILD.bazel deleted file mode 100644 index e544b62c3..000000000 --- a/src/uis/nvcf-ui/rules/oci/private/BUILD.bazel +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Private OCI rule implementations. - -filegroup( - name = "sources", - srcs = glob(["*.bzl", "*.bazel"]), - visibility = ["//visibility:public"], -) diff --git a/src/uis/nvcf-ui/rules/oci/private/common.bzl b/src/uis/nvcf-ui/rules/oci/private/common.bzl deleted file mode 100644 index 1c41dd002..000000000 --- a/src/uis/nvcf-ui/rules/oci/private/common.bzl +++ /dev/null @@ -1,130 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"Shared helpers for OCI image rules." - -load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template") -load("@aspect_bazel_lib//lib:transitions.bzl", "platform_transition_filegroup") -load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index", "oci_load", "oci_push") -load("//rules/oci:transition.bzl", "multi_arch") - -# nvcf-ui pulls the distroless-static base (matches the Dockerfile). The -# umbrella defaults this to @ubuntu_noble, but that base is not pulled here. -DEFAULT_BASE = "@distroless_static" -DEFAULT_PLATFORMS = [ - "//platforms:linux_arm64", - "//platforms:linux_x86_64", -] - -COMMON_LAYERS = [] - -def create_oci_image( - name, - tars, - base, - entrypoint, - visibility, - registry = None, - tags = None, - repo_tag = None, - workdir = None): - """Creates OCI image targets with platform transitions and tarball output. - - Generates: - - {name}: Platform-transitioned OCI image (for local builds) - - {name}_index: Multi-arch image index (amd64 + arm64) - - {name}_load: Local docker load target - - {name}.tar: Tarball filegroup - - {name}_push: Push to registry (if registry is set) - """ - all_tags = ["manual"] + (tags or []) - - pre_transitioned = name + "_pre_transitioned" - oci_image( - name = pre_transitioned, - base = base, - tars = tars + COMMON_LAYERS, - entrypoint = entrypoint, - workdir = workdir, - visibility = ["//visibility:private"], - tags = all_tags, - ) - - platform_transition_filegroup( - name = name, - srcs = [pre_transitioned], - target_platform = select({ - "@platforms//cpu:arm64": "//platforms:linux_arm64", - "@platforms//cpu:x86_64": "//platforms:linux_x86_64", - }), - visibility = visibility, - tags = all_tags, - ) - - multi_arch_name = name + "_multi_arch" - multi_arch( - name = multi_arch_name, - image = pre_transitioned, - platforms = DEFAULT_PLATFORMS, - visibility = ["//visibility:private"], - tags = all_tags, - ) - - oci_image_index( - name = name + "_index", - images = [multi_arch_name], - visibility = visibility, - tags = all_tags, - ) - - # repo_tag defaults to ":latest"; callers at the repo root (empty - # package name) must pass an explicit repo_tag since ":latest" is invalid. - load_name = name + "_load" - oci_load( - name = load_name, - image = name, - repo_tags = [repo_tag or (native.package_name() + ":latest")], - visibility = visibility, - tags = all_tags, - ) - - native.filegroup( - name = name + ".tar", - srcs = [load_name], - output_group = "tarball", - visibility = visibility, - tags = all_tags, - ) - - if registry: - stamped_tags = name + "_stamped_tags" - expand_template( - name = stamped_tags, - out = name + "_tags.txt", - stamp_substitutions = { - "{VERSION}": "{{STABLE_VERSION}}", - }, - template = ["{VERSION}"], - visibility = ["//visibility:private"], - ) - - oci_push( - name = name + "_push", - image = name + "_index", - remote_tags = stamped_tags, - repository = registry, - visibility = visibility, - tags = all_tags, - ) diff --git a/src/uis/nvcf-ui/rules/oci/transition.bzl b/src/uis/nvcf-ui/rules/oci/transition.bzl deleted file mode 100644 index d36ab64bd..000000000 --- a/src/uis/nvcf-ui/rules/oci/transition.bzl +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"Multi-arch transition rule for OCI images." - -def _multiarch_transition(settings, attr): - return [ - {"//command_line_option:platforms": str(platform)} - for platform in attr.platforms - ] - -multiarch_transition = transition( - implementation = _multiarch_transition, - inputs = [], - outputs = ["//command_line_option:platforms"], -) - -def _multi_arch_impl(ctx): - return DefaultInfo(files = depset(ctx.files.image)) - -multi_arch = rule( - implementation = _multi_arch_impl, - attrs = { - "image": attr.label(cfg = multiarch_transition), - "platforms": attr.label_list(), - "_allowlist_function_transition": attr.label( - default = "@bazel_tools//tools/allowlists/function_transition_allowlist", - ), - }, -) diff --git a/src/uis/nvcf-ui/ui/BUILD.bazel b/src/uis/nvcf-ui/ui/BUILD.bazel index 92ad05821..2f5110c51 100644 --- a/src/uis/nvcf-ui/ui/BUILD.bazel +++ b/src/uis/nvcf-ui/ui/BUILD.bazel @@ -14,8 +14,8 @@ # limitations under the License. load("@npm//:defs.bzl", "npm_link_all_packages") -load("@npm//ui:vite/package_json.bzl", vite_bin = "bin") -load("@npm//ui:vitest/package_json.bzl", vitest_bin = "bin") +load("@npm//src/uis/nvcf-ui/ui:vite/package_json.bzl", vite_bin = "bin") +load("@npm//src/uis/nvcf-ui/ui:vitest/package_json.bzl", vitest_bin = "bin") exports_files(["pnpm-lock.yaml"]) diff --git a/tools/ci/nested-modules.txt b/tools/ci/nested-modules.txt index 8863ad3c4..e6746d596 100644 --- a/tools/ci/nested-modules.txt +++ b/tools/ci/nested-modules.txt @@ -37,13 +37,6 @@ vendored src/compute-plane-services/nvca/vendor/cel.dev/expr scaffolding rules/oci-destinations -# nvcf-ui arrived from ngc/apps/nvcf-ui with its own module already in place -# (aspect_rules_js + rules_nodejs). Its BUILD files use labels relative to that -# module root, so merging into the root module means rewriting every label and -# validating with a Node/pnpm build. Ledger, not exception: the work is -# outstanding, not excused. Tracked in NVIDIA/nvcf#634. -ledger src/uis/nvcf-ui - # No permanent exceptions today. nvsnap was the open question in Phase 1 and it # migrated in NVIDIA/nvcf#471, so it is absent from this file entirely, which is # what a completed migration looks like here. diff --git a/tools/collect-dependencies/go.sum b/tools/collect-dependencies/go.sum index ac6428433..eaefe6e3e 100644 --- a/tools/collect-dependencies/go.sum +++ b/tools/collect-dependencies/go.sum @@ -1,4 +1,5 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= From 596c00422a71d1667fe7c337fa1b0c44e1f8e6cf Mon Sep 17 00:00:00 2001 From: balaji Date: Tue, 4 Aug 2026 14:12:05 -0700 Subject: [PATCH 4/6] fix(nvcf-ui): pin the backend's k8s deps to the repo's versions `bazel (root)` failed while `bazel (nvcf-ui)` passed. Adding the backend to go.work.bazel changes dependency resolution for every Go module in the repository, not just this subtree, and MVS took k8s.io/apiextensions-apiserver from v0.34.1 to v0.36.0 because this module still required k8s.io at v0.36.0. A version-specific replace pins client-go to v0.34.2, so the v0.36 apiextensions code referenced symbols that version does not have. Pins k8s.io/api, apimachinery and client-go to v0.34.2 and apiextensions-apiserver to v0.34.1, matching the rest of the repository. Applied with `go mod edit` and deliberately no `go mod tidy`: tidy re-resolves these upward from the transitive graph, which is what silently undid the equivalent fix for controller-runtime earlier. Verified with a full `bazel build //...` rather than the subtree alone. The subtree-only build is what let this through: it cannot observe a change in workspace-wide version resolution. Co-authored-by: Balaji Ganesan --- src/uis/nvcf-ui/backend/go.mod | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uis/nvcf-ui/backend/go.mod b/src/uis/nvcf-ui/backend/go.mod index bac0b42f5..ff3d27062 100644 --- a/src/uis/nvcf-ui/backend/go.mod +++ b/src/uis/nvcf-ui/backend/go.mod @@ -6,9 +6,9 @@ require ( github.com/fsnotify/fsnotify v1.10.1 github.com/rs/zerolog v1.35.1 go.yaml.in/yaml/v3 v3.0.4 - k8s.io/api v0.36.0 - k8s.io/apimachinery v0.36.0 - k8s.io/client-go v0.36.0 + k8s.io/api v0.34.2 + k8s.io/apimachinery v0.34.2 + k8s.io/client-go v0.34.2 sigs.k8s.io/controller-runtime v0.22.5 ) @@ -55,7 +55,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.36.0 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect From f426c954c0d195b21e1ec5a0412684a90bf83184 Mon Sep 17 00:00:00 2001 From: balaji Date: Tue, 4 Aug 2026 16:11:25 -0700 Subject: [PATCH 5/6] fix(nvcf-cli): make the auth-credentials test isolate its state file TestLoadConfigAuthCredentials failed in CI on this branch while passing on main, and the difference was not the code. On main the test is a remote-cache hit on every run and has never actually executed in CI. Changing go.work.bazel here invalidated the cache key, so this is its first real run. The test is not hermetic. state.DefaultStateManager is constructed at package init, capturing $HOME before any test can change it, so the test's t.Setenv("HOME", t.TempDir()) does not move the state path. LoadConfig falls back to that state file for credentials, so a stray ~/.nvcf-cli.state supplies an API key: "nothing set" then finds credentials, and "complete oauth2 setup" resolves to bearer instead of oauth2. CI runs tests with HOME=/tmp, which is shared and writable. Adds state.ResetDefaultStateManager so a test can rebuild the manager after pointing HOME somewhere private, and calls it at both sites that override HOME. Production behaviour is unchanged: a CLI process's home directory does not change after start, and nothing outside tests calls it. Reproduced before fixing by seeding $HOME/.nvcf-cli.state with an apiKey and running against main, which fails exactly as CI did; the same command passes with this change. Full suite: 21 of 21 pass with --nocache_test_results. Worth noting beyond this test: cache hits can mask tests that would fail if they ran. A periodic uncached run on main would surface others like it. Co-authored-by: Balaji Ganesan --- .../nvcf-cli/internal/client/client_test.go | 6 +++ src/clis/nvcf-cli/internal/state/state.go | 38 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/clis/nvcf-cli/internal/client/client_test.go b/src/clis/nvcf-cli/internal/client/client_test.go index fe23e6c08..1d42cf015 100644 --- a/src/clis/nvcf-cli/internal/client/client_test.go +++ b/src/clis/nvcf-cli/internal/client/client_test.go @@ -1210,6 +1210,9 @@ func TestLoadConfigUsesStateForActiveConfigFile(t *testing.T) { t.Setenv("NVCF_TOKEN", "") t.Setenv("NVCF_API_KEY", "") t.Setenv("HOME", t.TempDir()) + // Rebuild the state manager so it points at the temp HOME above; + // it is otherwise built at package init and reads the real one. + state.ResetDefaultStateManager() configPath := filepath.Join(t.TempDir(), "nvcf-cli-local.yaml") configBody := []byte(` @@ -1306,6 +1309,9 @@ func TestLoadConfigAuthCredentials(t *testing.T) { viper.AutomaticEnv() t.Cleanup(func() { viper.Reset() }) t.Setenv("HOME", t.TempDir()) + // Rebuild the state manager so it points at the temp HOME above; + // it is otherwise built at package init and reads the real one. + state.ResetDefaultStateManager() for _, key := range []string{"NVCF_API_KEY", "NVCF_TOKEN", "NVCF_OAUTH2_CLIENT_ID", "NVCF_OAUTH2_CLIENT_SECRET", "NVCF_OAUTH2_TOKEN_ENDPOINT"} { t.Setenv(key, "") } diff --git a/src/clis/nvcf-cli/internal/state/state.go b/src/clis/nvcf-cli/internal/state/state.go index a3951a65a..527b88f15 100644 --- a/src/clis/nvcf-cli/internal/state/state.go +++ b/src/clis/nvcf-cli/internal/state/state.go @@ -34,8 +34,8 @@ import ( // here. omitempty means old state files (without this field) load cleanly with // SelfHostedAuth == nil. type SelfHostedAuth struct { - Token string `json:"token,omitempty"` - ExpiresAt time.Time `json:"expiresAt,omitempty"` + Token string `json:"token,omitempty"` + ExpiresAt time.Time `json:"expiresAt,omitempty"` Fingerprint *FingerprintRef `json:"fingerprint,omitempty"` } @@ -372,6 +372,20 @@ func formatTime(t time.Time) string { // Global state manager instance (default context) var DefaultStateManager = NewStateManager() +// ResetDefaultStateManager rebuilds the default state manager, re-reading the +// home directory. +// +// The default manager is constructed at package init, so it captures $HOME +// before a test can change it. Without this, a test that points HOME at a temp +// directory still reads the real ~/.nvcf-cli.state, and any credentials there +// leak into it through the state fallback in LoadConfig. +// +// Production never calls this: a CLI process's home directory does not change +// after start. +func ResetDefaultStateManager() { + DefaultStateManager = NewStateManager() +} + // GetStateManagerForCurrentConfig returns a state manager for the current config context // This function should be called from commands that need config-aware state management func GetStateManagerForCurrentConfig() *StateManager { @@ -402,13 +416,13 @@ func SetConfig(configFile, kubeconfigPath string, clusterMode bool) { func SetEndpoints(baseURL, invokeURL, account string) { DefaultStateManager.SetEndpoints(baseURL, invokeURL, account) } -func HasFunction() bool { return DefaultStateManager.HasFunction() } -func IsTokenValid() bool { return DefaultStateManager.IsTokenValid() } -func IsAPIKeyValid() bool { return DefaultStateManager.IsAPIKeyValid() } -func PrintStatus() { DefaultStateManager.PrintStatus() } -func SetTask(taskID, taskName string) { DefaultStateManager.SetTask(taskID, taskName) } -func ClearTask() { DefaultStateManager.ClearTask() } -func HasTask() bool { return DefaultStateManager.HasTask() } -func SetNVCTAPIKey(key string, exp time.Time) { DefaultStateManager.SetNVCTAPIKey(key, exp) } -func ClearNVCTAPIKey() { DefaultStateManager.ClearNVCTAPIKey() } -func HasNVCTAPIKey() bool { return DefaultStateManager.HasNVCTAPIKey() } +func HasFunction() bool { return DefaultStateManager.HasFunction() } +func IsTokenValid() bool { return DefaultStateManager.IsTokenValid() } +func IsAPIKeyValid() bool { return DefaultStateManager.IsAPIKeyValid() } +func PrintStatus() { DefaultStateManager.PrintStatus() } +func SetTask(taskID, taskName string) { DefaultStateManager.SetTask(taskID, taskName) } +func ClearTask() { DefaultStateManager.ClearTask() } +func HasTask() bool { return DefaultStateManager.HasTask() } +func SetNVCTAPIKey(key string, exp time.Time) { DefaultStateManager.SetNVCTAPIKey(key, exp) } +func ClearNVCTAPIKey() { DefaultStateManager.ClearNVCTAPIKey() } +func HasNVCTAPIKey() bool { return DefaultStateManager.HasNVCTAPIKey() } From 28a08a1f88709188f14483152bda0ca2fffefa4a Mon Sep 17 00:00:00 2001 From: balaji Date: Wed, 5 Aug 2026 16:34:43 -0700 Subject: [PATCH 6/6] ci(bazel): gate Java services on the framework row when nv-boot-parent changes Sanjay observed nv-boot-parent building alongside the services that depend on it. Measuring three runs shows it is worse than concurrent: it reliably finishes last. services start +0s +5s +6s +80s nv-boot-parent +228s +0s +0s +2s +33s +465s +0s +6s +7s +7s +271s Two things combine. The matrix is built from `find src -name bazel-java-ci.json | LC_ALL=C sort`, which puts src/libraries/java/nv-boot-parent after src/control-plane-services/*, so the framework is the last of five rows. The docker-host lane caps at max-parallel 4. The framework therefore cannot start until a service frees a slot, and the services compile against whatever the cache already held. The framework row now runs as its own job and the service rows depend on it. The gate is conditional: the framework job only exists when change detection selected it, which is exactly when nv-boot-parent changed. When it did not, that job is skipped and the services start immediately, so ordinary runs pay nothing. When it did, services wait the 2.4 to 5.1 minutes the framework takes and then compile against the library they were meant to. `always()` on the service job is required rather than stylistic: a skipped dependency would otherwise skip every service, which is the common case. To avoid duplicating the job, its 131 step lines move to a composite action at .github/actions/bazel-java-build. Both jobs are thin callers, so the workflow is 62 lines added against 128 removed. The steps were extracted mechanically, not retyped; matrix.subtree.* became inputs.*, and every run: step gained the explicit shell: composite actions require. The required-checks aggregator now evaluates the framework result. Without it a framework failure would skip the service rows and the aggregate would pass on their skipped state. actionlint is clean. Not yet exercised on a branch that actually changes nv-boot-parent, which is the run that proves the gate fires; that is the next step, not something this commit claims. Co-authored-by: Balaji Ganesan --- .github/actions/bazel-java-build/action.yml | 165 +++++++++++++++++ .github/workflows/bazel.yml | 190 +++++++------------- 2 files changed, 227 insertions(+), 128 deletions(-) create mode 100644 .github/actions/bazel-java-build/action.yml diff --git a/.github/actions/bazel-java-build/action.yml b/.github/actions/bazel-java-build/action.yml new file mode 100644 index 000000000..b02eb46fb --- /dev/null +++ b/.github/actions/bazel-java-build/action.yml @@ -0,0 +1,165 @@ +# Build and test one Java component with Bazel on a bare runner. +# +# Extracted so the framework row (nv-boot-parent) and the service rows can run +# as two jobs without duplicating 130 lines of setup. The services gate on the +# framework job, so when nv-boot-parent changes it finishes before anything +# compiles against it. +name: bazel-java-build +description: Build and test a Java component with Bazel on a docker-host runner. + +inputs: + id: + description: Component id, e.g. cloud-tasks. + required: true + path: + description: Component path, e.g. src/control-plane-services/cloud-tasks. + required: true + workdir: + description: Directory to invoke Bazel from. + required: true + scope: + description: Bazel target pattern for this component. + required: true + component_kind: + description: java-framework or java-service. + required: true + +runs: + using: composite + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + # The Java tree builds with local_jdk (root .bazelrc). Unlike the fast + # matrix, this lane runs on a bare runner rather than the bazel-ci image, + # so provide JDK 25 explicitly for the Java requires-docker tests. + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '25' + - name: Install bazelisk + shell: bash + run: | + sudo curl -fsSLo /usr/local/bin/bazel \ + https://github.com/bazelbuild/bazelisk/releases/download/v1.20.0/bazelisk-linux-amd64 + sudo chmod +x /usr/local/bin/bazel + bazel version + # Cache Bazel's install base + repository cache (external deps: Maven + # artifacts, toolchains) across runs. Compiled action outputs come from + # the remote cache below. + - name: Cache Bazel repository + disk caches + uses: actions/cache@v4 + with: + path: | + ~/.cache/bazel/*/install + ~/.cache/bazel/*/cache + key: bazel-docker-${{ inputs.workdir == '.' && 'rootmodule' || inputs.id }}-${{ hashFiles(format('{0}/MODULE.bazel.lock', inputs.workdir), format('{0}/.bazelversion', inputs.workdir)) }} + restore-keys: | + bazel-docker-${{ inputs.workdir == '.' && 'rootmodule' || inputs.id }}- + # Enable the public EC2 Buildbarn remote cache when the token+endpoint are + # present. Upload only on main pushes so PRs are read-only and cannot + # poison the shared cache. + - name: Prepare remote cache + shell: bash + run: | + if [ -n "$CACHE_TOKEN" ] && [ -n "$CACHE_ENDPOINT" ] && [ -n "$CACHE_CA" ]; then + printf '%s\n' "$CACHE_CA" > "$RUNNER_TEMP/cache-ca.pem" + echo "CACHE_READY=1" >> "$GITHUB_ENV" + upload=false + upload="$(bash "$GITHUB_WORKSPACE/tools/ci/bazel-cache-upload-mode")" + echo "CACHE_UPLOAD=$upload" >> "$GITHUB_ENV" + echo "remote cache ready (upload=$upload)" + else + echo "CACHE_READY=0" >> "$GITHUB_ENV" + echo "remote cache unavailable: cacheless build" + fi + - name: bazel build (${{ inputs.id }}) + working-directory: ${{ inputs.workdir }} + shell: bash + run: | + CACHE=(--remote_cache=) + if [ "${CACHE_READY:-0}" = "1" ]; then + CACHE=(--remote_cache="$CACHE_ENDPOINT" + --tls_certificate="$RUNNER_TEMP/cache-ca.pem" + --remote_header="authorization=Bearer $CACHE_TOKEN" + --remote_cache_compression --remote_download_all + --remote_timeout=600 --remote_retries=5) + if [ "${CACHE_UPLOAD:-false}" = "true" ]; then + CACHE+=(--remote_upload_local_results=true) + else + CACHE+=(--remote_upload_local_results=false) + fi + fi + # Capture output so a remote-cache transport error is told apart from a + # genuine build failure: retry cacheless only on the former, otherwise + # every broken PR builds twice (CodeRabbit review on #399). The grep is + # broad on purpose -- a false positive only costs a second attempt (the + # old always-retry behavior), a false negative never hides a real break. + set +e + bazel build "${CACHE[@]}" ${{ inputs.scope }} 2>&1 | tee "$RUNNER_TEMP/bazel-build.log" + rc=${PIPESTATUS[0]} + set -e + if [ "$rc" -ne 0 ] && [ "${CACHE_READY:-0}" = "1" ] && grep -qiE \ + 'remote (cache|spawn)|StatusRuntimeException|UNAVAILABLE|DEADLINE_EXCEEDED|Bad Gateway|502|lost inputs|BulkTransfer|Failed to (query remote|init TLS)' \ + "$RUNNER_TEMP/bazel-build.log"; then + echo "::warning::remote cache error (exit $rc); retrying cacheless" + bazel build --remote_cache= ${{ inputs.scope }} + rc=$? + fi + exit "$rc" + - name: bazel test (${{ inputs.id }}) + working-directory: ${{ inputs.workdir }} + # Full suite, NO tag filter: unit AND requires-docker (Testcontainers) + # tests against the host Docker daemon. bazel exit 4 (no test targets in + # scope) is treated as success. + shell: bash + run: | + CACHE=(--remote_cache=) + if [ "${CACHE_READY:-0}" = "1" ]; then + CACHE=(--remote_cache="$CACHE_ENDPOINT" + --tls_certificate="$RUNNER_TEMP/cache-ca.pem" + --remote_header="authorization=Bearer $CACHE_TOKEN" + --remote_cache_compression --remote_download_all + --remote_timeout=600 --remote_retries=5) + if [ "${CACHE_UPLOAD:-false}" = "true" ]; then + CACHE+=(--remote_upload_local_results=true) + else + CACHE+=(--remote_upload_local_results=false) + fi + fi + set +e + bazel test "${CACHE[@]}" ${{ inputs.scope }} \ + --test_output=errors --flaky_test_attempts=2 2>&1 | tee "$RUNNER_TEMP/bazel-test.log" + rc=${PIPESTATUS[0]} + set -e + [ "$rc" -eq 4 ] && { echo "::warning::${{ inputs.id }}: no test targets in scope"; exit 0; } + # Retry cacheless only on a remote-cache transport error, never on a + # genuine test failure (exit 3) or build failure (1); see the build + # step above for the rationale. + if [ "$rc" -ne 0 ] && [ "${CACHE_READY:-0}" = "1" ] && grep -qiE \ + 'remote (cache|spawn)|StatusRuntimeException|UNAVAILABLE|DEADLINE_EXCEEDED|Bad Gateway|502|lost inputs|BulkTransfer|Failed to (query remote|init TLS)' \ + "$RUNNER_TEMP/bazel-test.log"; then + echo "::warning::remote cache error (exit $rc); retrying cacheless" + set +e; bazel test --remote_cache= ${{ inputs.scope }} --test_output=errors --flaky_test_attempts=2; rc=$?; set -e + [ "$rc" -eq 4 ] && exit 0 + fi + exit "$rc" + + - name: Stage Java verification artifacts + if: ${{ always() && startsWith(inputs.component_kind, 'java-') }} + working-directory: ${{ inputs.workdir }} + shell: bash + run: | + bash tools/ci/stage-bazel-java-artifacts \ + "${{ inputs.id }}" \ + "${{ inputs.path }}" + + - name: Upload Java verification artifacts + if: ${{ always() && startsWith(inputs.component_kind, 'java-') }} + uses: actions/upload-artifact@v4 + with: + name: bazel-${{ inputs.id }}-verification-${{ github.run_attempt }} + path: ${{ runner.temp }}/bazel-java-verification/${{ inputs.id }} + if-no-files-found: error + retention-days: 14 + diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 4ae3a1919..cc926b9ad 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -73,6 +73,8 @@ jobs: outputs: matrix: ${{ steps.detect.outputs.matrix }} matrix_docker: ${{ steps.detect.outputs.matrix_docker }} + matrix_docker_framework: ${{ steps.detect.outputs.matrix_docker_framework }} + matrix_docker_service: ${{ steps.detect.outputs.matrix_docker_service }} any: ${{ steps.detect.outputs.any }} steps: - uses: actions/checkout@v4 @@ -379,6 +381,16 @@ jobs: echo "selected ${total} subtree(s): build-container=[$(printf '%s' "$include" | jq -r '[.[].id] | join(", ")')] docker-host=[$(printf '%s' "$include_docker" | jq -r '[.[].id] | join(", ")')]" echo "matrix=${include}" >> "$GITHUB_OUTPUT" echo "matrix_docker=${include_docker}" >> "$GITHUB_OUTPUT" + # nv-boot-parent is a java-framework that the Java services compile + # against. Split it out so the services can gate on it finishing rather + # than racing it: it sorts last by path (src/libraries/... after + # src/control-plane-services/...) and, with max-parallel below the row + # count, could not start until a service freed a slot, so it reliably + # finished last and never warmed the cache for its own consumers. + include_docker_fw=$(printf '%s' "$include_docker" | jq -c '[.[] | select(.component_kind == "java-framework")]') + include_docker_svc=$(printf '%s' "$include_docker" | jq -c '[.[] | select(.component_kind != "java-framework")]') + echo "matrix_docker_framework=${include_docker_fw}" >> "$GITHUB_OUTPUT" + echo "matrix_docker_service=${include_docker_svc}" >> "$GITHUB_OUTPUT" if [ "$total" -gt 0 ]; then echo "any=true" >> "$GITHUB_OUTPUT"; else echo "any=false" >> "$GITHUB_OUTPUT"; fi bazel: @@ -875,10 +887,10 @@ jobs: # excluded from the build-container matrix, so they never produce a # zero-test lane. # bazelisk reads .bazelversion for the pinned Bazel. - bazel-docker: + bazel-java-framework: name: bazel (${{ matrix.subtree.id }}) needs: detect - if: needs.detect.outputs.matrix_docker != '[]' + if: needs.detect.outputs.matrix_docker_framework != '[]' runs-on: ubuntu-latest # Same public EC2 Buildbarn remote cache the container matrix uses, sourced # from a repo secret/var. Bare runners reach it over grpcs with a bearer @@ -895,142 +907,55 @@ jobs: # do not add to the simultaneous actions/checkout burst. max-parallel: 4 matrix: - subtree: ${{ fromJson(needs.detect.outputs.matrix_docker) }} + subtree: ${{ fromJson(needs.detect.outputs.matrix_docker_framework) }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - # The Java tree builds with local_jdk (root .bazelrc). Unlike the fast - # matrix, this lane runs on a bare runner rather than the bazel-ci image, - # so provide JDK 25 explicitly for the Java requires-docker tests. - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: '25' - - name: Install bazelisk - run: | - sudo curl -fsSLo /usr/local/bin/bazel \ - https://github.com/bazelbuild/bazelisk/releases/download/v1.20.0/bazelisk-linux-amd64 - sudo chmod +x /usr/local/bin/bazel - bazel version - # Cache Bazel's install base + repository cache (external deps: Maven - # artifacts, toolchains) across runs. Compiled action outputs come from - # the remote cache below. - - name: Cache Bazel repository + disk caches - uses: actions/cache@v4 + - uses: ./.github/actions/bazel-java-build with: - path: | - ~/.cache/bazel/*/install - ~/.cache/bazel/*/cache - key: bazel-docker-${{ matrix.subtree.workdir == '.' && 'rootmodule' || matrix.subtree.id }}-${{ hashFiles(format('{0}/MODULE.bazel.lock', matrix.subtree.workdir), format('{0}/.bazelversion', matrix.subtree.workdir)) }} - restore-keys: | - bazel-docker-${{ matrix.subtree.workdir == '.' && 'rootmodule' || matrix.subtree.id }}- - # Enable the public EC2 Buildbarn remote cache when the token+endpoint are - # present. Upload only on main pushes so PRs are read-only and cannot - # poison the shared cache. - - name: Prepare remote cache - run: | - if [ -n "$CACHE_TOKEN" ] && [ -n "$CACHE_ENDPOINT" ] && [ -n "$CACHE_CA" ]; then - printf '%s\n' "$CACHE_CA" > "$RUNNER_TEMP/cache-ca.pem" - echo "CACHE_READY=1" >> "$GITHUB_ENV" - upload=false - upload="$(bash "$GITHUB_WORKSPACE/tools/ci/bazel-cache-upload-mode")" - echo "CACHE_UPLOAD=$upload" >> "$GITHUB_ENV" - echo "remote cache ready (upload=$upload)" - else - echo "CACHE_READY=0" >> "$GITHUB_ENV" - echo "remote cache unavailable: cacheless build" - fi - - name: bazel build (${{ matrix.subtree.id }}) - working-directory: ${{ matrix.subtree.workdir }} - run: | - CACHE=(--remote_cache=) - if [ "${CACHE_READY:-0}" = "1" ]; then - CACHE=(--remote_cache="$CACHE_ENDPOINT" - --tls_certificate="$RUNNER_TEMP/cache-ca.pem" - --remote_header="authorization=Bearer $CACHE_TOKEN" - --remote_cache_compression --remote_download_all - --remote_timeout=600 --remote_retries=5) - if [ "${CACHE_UPLOAD:-false}" = "true" ]; then - CACHE+=(--remote_upload_local_results=true) - else - CACHE+=(--remote_upload_local_results=false) - fi - fi - # Capture output so a remote-cache transport error is told apart from a - # genuine build failure: retry cacheless only on the former, otherwise - # every broken PR builds twice (CodeRabbit review on #399). The grep is - # broad on purpose -- a false positive only costs a second attempt (the - # old always-retry behavior), a false negative never hides a real break. - set +e - bazel build "${CACHE[@]}" ${{ matrix.subtree.scope }} 2>&1 | tee "$RUNNER_TEMP/bazel-build.log" - rc=${PIPESTATUS[0]} - set -e - if [ "$rc" -ne 0 ] && [ "${CACHE_READY:-0}" = "1" ] && grep -qiE \ - 'remote (cache|spawn)|StatusRuntimeException|UNAVAILABLE|DEADLINE_EXCEEDED|Bad Gateway|502|lost inputs|BulkTransfer|Failed to (query remote|init TLS)' \ - "$RUNNER_TEMP/bazel-build.log"; then - echo "::warning::remote cache error (exit $rc); retrying cacheless" - bazel build --remote_cache= ${{ matrix.subtree.scope }} - rc=$? - fi - exit "$rc" - - name: bazel test (${{ matrix.subtree.id }}) - working-directory: ${{ matrix.subtree.workdir }} - # Full suite, NO tag filter: unit AND requires-docker (Testcontainers) - # tests against the host Docker daemon. bazel exit 4 (no test targets in - # scope) is treated as success. - run: | - CACHE=(--remote_cache=) - if [ "${CACHE_READY:-0}" = "1" ]; then - CACHE=(--remote_cache="$CACHE_ENDPOINT" - --tls_certificate="$RUNNER_TEMP/cache-ca.pem" - --remote_header="authorization=Bearer $CACHE_TOKEN" - --remote_cache_compression --remote_download_all - --remote_timeout=600 --remote_retries=5) - if [ "${CACHE_UPLOAD:-false}" = "true" ]; then - CACHE+=(--remote_upload_local_results=true) - else - CACHE+=(--remote_upload_local_results=false) - fi - fi - set +e - bazel test "${CACHE[@]}" ${{ matrix.subtree.scope }} \ - --test_output=errors --flaky_test_attempts=2 2>&1 | tee "$RUNNER_TEMP/bazel-test.log" - rc=${PIPESTATUS[0]} - set -e - [ "$rc" -eq 4 ] && { echo "::warning::${{ matrix.subtree.id }}: no test targets in scope"; exit 0; } - # Retry cacheless only on a remote-cache transport error, never on a - # genuine test failure (exit 3) or build failure (1); see the build - # step above for the rationale. - if [ "$rc" -ne 0 ] && [ "${CACHE_READY:-0}" = "1" ] && grep -qiE \ - 'remote (cache|spawn)|StatusRuntimeException|UNAVAILABLE|DEADLINE_EXCEEDED|Bad Gateway|502|lost inputs|BulkTransfer|Failed to (query remote|init TLS)' \ - "$RUNNER_TEMP/bazel-test.log"; then - echo "::warning::remote cache error (exit $rc); retrying cacheless" - set +e; bazel test --remote_cache= ${{ matrix.subtree.scope }} --test_output=errors --flaky_test_attempts=2; rc=$?; set -e - [ "$rc" -eq 4 ] && exit 0 - fi - exit "$rc" + id: ${{ matrix.subtree.id }} + path: ${{ matrix.subtree.path }} + workdir: ${{ matrix.subtree.workdir }} + scope: ${{ matrix.subtree.scope }} + component_kind: ${{ matrix.subtree.component_kind }} - - name: Stage Java verification artifacts - if: ${{ always() && startsWith(matrix.subtree.component_kind, 'java-') }} - working-directory: ${{ matrix.subtree.workdir }} - run: | - bash tools/ci/stage-bazel-java-artifacts \ - "${{ matrix.subtree.id }}" \ - "${{ matrix.subtree.path }}" - - - name: Upload Java verification artifacts - if: ${{ always() && startsWith(matrix.subtree.component_kind, 'java-') }} - uses: actions/upload-artifact@v4 + bazel-docker: + name: bazel (${{ matrix.subtree.id }}) + needs: [detect, bazel-java-framework] + if: always() && needs.detect.result == 'success' && needs.bazel-java-framework.result != 'failure' && needs.bazel-java-framework.result != 'cancelled' && needs.detect.outputs.matrix_docker_service != '[]' + runs-on: ubuntu-latest + # Same public EC2 Buildbarn remote cache the container matrix uses, sourced + # from a repo secret/var. Bare runners reach it over grpcs with a bearer + # token; fork PRs have no secret and fall back to cacheless automatically. + env: + CACHE_TOKEN: ${{ secrets.BAZEL_REMOTE_CACHE_TOKEN }} + CACHE_ENDPOINT: ${{ vars.BAZEL_REMOTE_CACHE_ENDPOINT }} + # Passed as an env var, never interpolated inline into a run: script, so + # its contents can never be parsed as shell (zizmor template-injection). + CACHE_CA: ${{ vars.BAZEL_REMOTE_CACHE_CA }} + strategy: + fail-fast: false + # Cap concurrency here too (see the bazel job) so the docker-host rows + # do not add to the simultaneous actions/checkout burst. + max-parallel: 4 + matrix: + subtree: ${{ fromJson(needs.detect.outputs.matrix_docker_service) }} + steps: + - uses: actions/checkout@v4 with: - name: bazel-${{ matrix.subtree.id }}-verification-${{ github.run_attempt }} - path: ${{ runner.temp }}/bazel-java-verification/${{ matrix.subtree.id }} - if-no-files-found: error - retention-days: 14 + fetch-depth: 0 + - uses: ./.github/actions/bazel-java-build + with: + id: ${{ matrix.subtree.id }} + path: ${{ matrix.subtree.path }} + workdir: ${{ matrix.subtree.workdir }} + scope: ${{ matrix.subtree.scope }} + component_kind: ${{ matrix.subtree.component_kind }} bazel-verification: name: bazel required checks - needs: [detect, bazel, bazel-docker] + needs: [detect, bazel, bazel-java-framework, bazel-docker] if: ${{ always() }} runs-on: ubuntu-latest steps: @@ -1038,6 +963,7 @@ jobs: env: DETECT_RESULT: ${{ needs.detect.result }} BAZEL_RESULT: ${{ needs.bazel.result }} + FRAMEWORK_RESULT: ${{ needs.bazel-java-framework.result }} DOCKER_RESULT: ${{ needs.bazel-docker.result }} BAZEL_ANY: ${{ needs.detect.outputs.any }} run: | @@ -1068,6 +994,14 @@ jobs: exit 1 fi + # The framework gate runs before the service rows. A failure here + # skips them, so without this check the aggregate would pass on a + # skipped bazel-docker while nv-boot-parent was actually broken. + echo "bazel-java-framework result: ${FRAMEWORK_RESULT}" + if [ "${FRAMEWORK_RESULT}" != "success" ] && [ "${FRAMEWORK_RESULT}" != "skipped" ]; then + echo "::error::bazel-java-framework did not pass (${FRAMEWORK_RESULT})" + exit 1 + fi echo "bazel-docker result: ${DOCKER_RESULT}" if [ "${DOCKER_RESULT}" != "success" ] && [ "${DOCKER_RESULT}" != "skipped" ]; then echo "one or more bazel docker (per-service Testcontainers) lanes failed or were cancelled"