mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Retire legacy settings infrastructure aliases
This commit is contained in:
@@ -87,14 +87,13 @@ management, and fleet control surfaces.
|
||||
60. `frontend-modern/src/utils/clusterEndpointPresentation.ts`
|
||||
61. `frontend-modern/src/utils/nodeModalPresentation.ts`
|
||||
62. `frontend-modern/src/utils/proxmoxSettingsPresentation.ts`
|
||||
63. `frontend-modern/src/components/Settings/platformConnectionsModel.ts`
|
||||
64. `frontend-modern/src/components/Settings/useTrueNASSettingsPanelState.ts`
|
||||
65. `frontend-modern/src/components/Settings/useVMwareSettingsPanelState.ts`
|
||||
66. `frontend-modern/src/components/Settings/MonitoredSystemImpactPreview.tsx`
|
||||
67. `internal/hostagent/proxmox_setup.go`
|
||||
68. `internal/remoteconfig/client.go`
|
||||
69. `internal/agenttls/config.go`
|
||||
70. `internal/api/agent_exec_token_binding.go`
|
||||
63. `frontend-modern/src/components/Settings/useTrueNASSettingsPanelState.ts`
|
||||
64. `frontend-modern/src/components/Settings/useVMwareSettingsPanelState.ts`
|
||||
65. `frontend-modern/src/components/Settings/MonitoredSystemImpactPreview.tsx`
|
||||
66. `internal/hostagent/proxmox_setup.go`
|
||||
67. `internal/remoteconfig/client.go`
|
||||
68. `internal/agenttls/config.go`
|
||||
69. `internal/api/agent_exec_token_binding.go`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
@@ -629,7 +628,7 @@ profile and assignment columns, but embedded table framing must route through
|
||||
Approval-gated command execution must expose stable rejection reasons for
|
||||
invalid approval grants so fleet operators can distinguish missing, expired,
|
||||
mismatched, and signature-invalid grants through agent metrics.
|
||||
9. Add or change profile management, the extracted agent profiles runtime owner, the agent profile settings catalog, the infrastructure source-manager landing, the pure unified-agent inventory/install model, the connections-ledger workspace shell, the unified ConnectionEditor and its per-type credential slots, route model, shared install section owner, the shared direct-node/discovery infrastructure settings owners plus their model, shared frontend install-command assembly, Proxmox setup/install API transport, TrueNAS platform-connection management, VMware platform-connection management, the shared monitored-system impact preview shell for those platform connections, setup-completion install handoff transport, deploy-fallback manual install transport, and fleet-control presentation through `frontend-modern/src/api/agentProfiles.ts`, `frontend-modern/src/api/nodes.ts`, `frontend-modern/src/components/Settings/AgentProfilesPanel.tsx`, `frontend-modern/src/components/Settings/agentProfileSettings.ts`, `frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts`, `frontend-modern/src/components/Settings/connectionsTableModel.ts`, `frontend-modern/src/components/Settings/useConnectionsLedger.ts`, `frontend-modern/src/components/Settings/useConnectionRowActions.ts`, `frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/AddressProbeStep.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/useConnectionEditor.ts`, `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx`, `frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx`, `frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx`, `frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx`, `frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx`, `frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts`, `frontend-modern/src/components/Settings/MonitoredSystemImpactPreview.tsx`, `frontend-modern/src/components/Settings/platformConnectionsModel.ts`, `frontend-modern/src/components/Settings/useTrueNASSettingsPanelState.ts`, `frontend-modern/src/components/Settings/useVMwareSettingsPanelState.ts`, `frontend-modern/src/components/Settings/proxmoxSettingsModel.ts`, `frontend-modern/src/components/Settings/ConfiguredNodeTables.tsx`, `frontend-modern/src/components/Settings/SettingsSectionNav.tsx`, `frontend-modern/src/components/Settings/infrastructureSettingsModel.ts`, `frontend-modern/src/components/Settings/useInfrastructureConfiguredNodesState.ts`, `frontend-modern/src/components/Settings/useInfrastructureDiscoveryRuntimeState.ts`, `frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx`, `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx`, `frontend-modern/src/components/Settings/useInfrastructureSettingsState.ts`, `frontend-modern/src/components/Settings/nodeModalModel.ts`, `frontend-modern/src/components/Settings/useNodeModalState.ts`, `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`, and `frontend-modern/src/utils/agentInstallCommand.ts`. Phase 9 retired the legacy reporting/inventory surface (InfrastructureOperationsController, InfrastructureInventorySection, InfrastructureActiveRowDetails, InfrastructureIgnoredRowDetails, InfrastructureStopMonitoringDialog, useInfrastructureReportingState) and the per-type shells (PlatformConnectionsWorkspace, ProxmoxSettingsPanel, ProxmoxDirectWorkspace, ProxmoxConfiguredNodesTable, ProxmoxDirectConnectionsCard, ProxmoxDiscoveryResultsCard, ProxmoxDeleteNodeDialog, ProxmoxNodeModalStack, NodeModal shell, TrueNASSettingsPanel, VMwareSettingsPanel, useProxmoxDirectWorkspaceState); lifecycle extensions must route through the unified aggregator ledger, source-manager cards, and ConnectionEditor credential slots rather than reintroducing those retired surfaces.
|
||||
9. Add or change profile management, the extracted agent profiles runtime owner, the agent profile settings catalog, the infrastructure source-manager landing, the pure unified-agent inventory/install model, the connections-ledger workspace shell, the unified ConnectionEditor and its per-type credential slots, route model, shared install section owner, the shared direct-node/discovery infrastructure settings owners plus their model, shared frontend install-command assembly, Proxmox setup/install API transport, TrueNAS platform-connection management, VMware platform-connection management, the shared monitored-system impact preview shell for those platform connections, setup-completion install handoff transport, deploy-fallback manual install transport, and fleet-control presentation through `frontend-modern/src/api/agentProfiles.ts`, `frontend-modern/src/api/nodes.ts`, `frontend-modern/src/components/Settings/AgentProfilesPanel.tsx`, `frontend-modern/src/components/Settings/agentProfileSettings.ts`, `frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts`, `frontend-modern/src/components/Settings/connectionsTableModel.ts`, `frontend-modern/src/components/Settings/useConnectionsLedger.ts`, `frontend-modern/src/components/Settings/useConnectionRowActions.ts`, `frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/AddressProbeStep.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/useConnectionEditor.ts`, `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx`, `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx`, `frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx`, `frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx`, `frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx`, `frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx`, `frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts`, `frontend-modern/src/components/Settings/MonitoredSystemImpactPreview.tsx`, `frontend-modern/src/components/Settings/useTrueNASSettingsPanelState.ts`, `frontend-modern/src/components/Settings/useVMwareSettingsPanelState.ts`, `frontend-modern/src/components/Settings/proxmoxSettingsModel.ts`, `frontend-modern/src/components/Settings/ConfiguredNodeTables.tsx`, `frontend-modern/src/components/Settings/SettingsSectionNav.tsx`, `frontend-modern/src/components/Settings/infrastructureSettingsModel.ts`, `frontend-modern/src/components/Settings/useInfrastructureConfiguredNodesState.ts`, `frontend-modern/src/components/Settings/useInfrastructureDiscoveryRuntimeState.ts`, `frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx`, `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx`, `frontend-modern/src/components/Settings/useInfrastructureSettingsState.ts`, `frontend-modern/src/components/Settings/nodeModalModel.ts`, `frontend-modern/src/components/Settings/useNodeModalState.ts`, `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`, and `frontend-modern/src/utils/agentInstallCommand.ts`. Phase 9 retired the legacy reporting/inventory surface (InfrastructureOperationsController, InfrastructureInventorySection, InfrastructureActiveRowDetails, InfrastructureIgnoredRowDetails, InfrastructureStopMonitoringDialog, useInfrastructureReportingState) and the per-type shells (PlatformConnectionsWorkspace, ProxmoxSettingsPanel, ProxmoxDirectWorkspace, ProxmoxConfiguredNodesTable, ProxmoxDirectConnectionsCard, ProxmoxDiscoveryResultsCard, ProxmoxDeleteNodeDialog, ProxmoxNodeModalStack, NodeModal shell, TrueNASSettingsPanel, VMwareSettingsPanel, useProxmoxDirectWorkspaceState); lifecycle extensions must route through the unified aggregator ledger, source-manager cards, and ConnectionEditor credential slots rather than reintroducing those retired surfaces.
|
||||
Those lifecycle-owned settings hooks may consume websocket state only through `frontend-modern/src/contexts/appRuntime.ts`; they must not import `frontend-modern/src/App.tsx` or recreate root-shell providers.
|
||||
Discovery configuration is part of that same lifecycle-owned workspace boundary. `InfrastructureSourceManager.tsx` must open one canonical discovery editor through `InfrastructureDiscoverySettingsDialog.tsx`, `DiscoverySettingsForm.tsx`, and `discoverySettingsModel.ts`, while the System/Network shell stays limited to network-boundary controls instead of reintroducing a second editable discovery surface. That same workspace boundary now owns the infrastructure source-management toolbar too: the landing page exposes `Add infrastructure`, `Run discovery`, and `Discovery settings` as first-viewport toolbar actions inside the source manager, while governed source rows expose per-source add actions such as `Install Pulse Agent` from the shared catalog. `Run discovery` must remain a visible text command for manual scans rather than an icon-only affordance. `Detect address` remains inside the single add-flow source picker/probe path rather than a duplicate toolbar action. The same landing boundary may surface setup confidence from the unified rows and discovered candidates, including connected-system count, API coverage, agent coverage, sources that still need an agent, and discovery review state, without creating a second inventory model or provider-specific summary fetch. Source groups must stay in the governed source catalog order instead of re-sorting by current row count, row-level lifecycle entry points must use `Manage` language, and locked agent-install states must show a compact command inventory without raw token placeholders or disabled copy commands until a token exists. Network discovery settings remain safety-critical: automatic scanning must surface the shared-network/subnet warning before operators save scan mode changes. The AI settings manual workload refresh at `/api/discovery/run` is a discovery/API refresh sweep, not an enrollment or source-admission path; lifecycle surfaces may observe the refreshed candidates but must not reinterpret it as agent install authority. Agent command-execution handoff copy belongs to that same install surface: `InfrastructureInstallerSection.tsx` may expose the Pulse command-execution toggle for Patrol, but the label must describe Patrol remediation rather than reviving `Patrol auto-fix` or implying a paid monitoring-volume gate.
|
||||
Setup-completion handoff belongs to that same single add-flow boundary. The first-run completion screen must keep credentials as the first surfaced object, then present one compact next-step surface that sends operators to Add infrastructure or directly to the Agent handoff; source-choice explanation may live inside that surface, but lifecycle work must not reintroduce a separate setup-wizard tour, duplicate CTA section, or inline install-command owner before the canonical infrastructure workspace.
|
||||
@@ -964,13 +963,14 @@ profile and assignment columns, but embedded table framing must route through
|
||||
and opens the saved-connection create flow for API-backed platforms on the
|
||||
same page. `/settings/infrastructure/install`,
|
||||
`/settings/infrastructure/platforms`, and
|
||||
`/settings/infrastructure/operations` remain valid deep links, but they
|
||||
must resolve to section focus on that same single-page workspace rather
|
||||
than rendering separate page shells or hiding the top ledger. Read-only
|
||||
sessions must redirect any non-inventory infrastructure deep link back to
|
||||
`/settings/infrastructure`, suppress the add-system entry point, and hide
|
||||
configuration-only sections so presentation-policy restrictions still
|
||||
hold. That top ledger must also stay readable inside the governed settings
|
||||
`/settings/infrastructure/operations` are retired settings aliases and must
|
||||
fail route eligibility instead of resolving to section focus or rendering
|
||||
separate page shells. The canonical add flow is
|
||||
`/settings/infrastructure?add=<step>`. Read-only sessions must redirect the
|
||||
canonical Infrastructure workspace back to `/settings/infrastructure`,
|
||||
suppress the add-system entry point, and hide configuration-only sections so
|
||||
presentation-policy restrictions still hold. That top ledger must also stay
|
||||
readable inside the governed settings
|
||||
shell at ordinary desktop widths: `InfrastructureSourceManager.tsx` must
|
||||
keep the compact row/card presentation readable without forcing horizontal
|
||||
scrolling just to reach primary controls. Dedicated collection and
|
||||
@@ -1243,8 +1243,8 @@ confirmation, preflight, retry, and status labels.
|
||||
|
||||
The infrastructure workspace collapsed to a single `/settings/infrastructure`
|
||||
route. `buildInfrastructureWorkspacePath()` always returns the base path;
|
||||
`deriveAddStepFromLegacyPath()` maps legacy sub-paths to in-page panel state.
|
||||
`SetupCompletionPanel.tsx` uses one `INFRASTRUCTURE_PATH` constant for all
|
||||
add flow state is carried only by the `add` query parameter on that base path.
|
||||
`SetupCompletionPanel.tsx` uses the shared infrastructure path builders for all
|
||||
install and platform CTAs.
|
||||
`frontend-modern/src/utils/infrastructureSettingsPresentation.ts` owns the
|
||||
customer-facing Settings Infrastructure target label and onboarding source
|
||||
@@ -1768,8 +1768,8 @@ The API-backed platform onboarding surface now lives inside the shared
|
||||
Infrastructure workspace. `InfrastructureSourceManager.tsx`,
|
||||
`connectionsTableModel.ts`, `InfrastructureWorkspace.tsx`,
|
||||
`infrastructureWorkspaceModel.ts`, `InfrastructureInstallerSection.tsx`,
|
||||
`platformConnectionsModel.ts`, `useInfrastructureSettingsState.ts`,
|
||||
`useTrueNASSettingsPanelState.ts`, `useVMwareSettingsPanelState.ts`,
|
||||
`useInfrastructureSettingsState.ts`, `useTrueNASSettingsPanelState.ts`,
|
||||
`useVMwareSettingsPanelState.ts`,
|
||||
`proxmoxSettingsModel.ts`, `useInfrastructureConfiguredNodesState.ts`, and
|
||||
`useInfrastructureDiscoveryRuntimeState.ts` own the fallback
|
||||
install/direct/reporting operator flow, with `InfrastructureSourceManager.tsx`
|
||||
@@ -3249,17 +3249,15 @@ The infrastructure workspace now uses a single flat route
|
||||
modal is open and which add step is active — is managed through the governed
|
||||
`InfrastructurePanelStep` query contract rather than URL segments.
|
||||
`frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts`
|
||||
exposes `buildInfrastructureWorkspacePath()` (always returns the base path
|
||||
regardless of argument) and `deriveAddStepFromLegacyPath()` (maps legacy deep
|
||||
URLs to panel state for backwards-compatible deep-link resolution) as the sole
|
||||
path-building and path-reading contract for lifecycle-adjacent install and
|
||||
setup surfaces. Callers that formerly passed `'platforms'` or `'install'` to
|
||||
`buildInfrastructureWorkspacePath` must use the no-argument form; the argument
|
||||
is accepted but ignored so link-site callers can be updated incrementally
|
||||
without breaking navigation.
|
||||
exposes `buildInfrastructureWorkspacePath()` and
|
||||
`buildInfrastructureOnboardingPath()` as the sole path-building contract for
|
||||
lifecycle-adjacent install and setup surfaces. Callers that formerly passed
|
||||
`'platforms'` or `'install'` to `buildInfrastructureWorkspacePath` must use the
|
||||
no-argument form or the query-backed onboarding builder; removed subpaths must
|
||||
not remain as compatibility hrefs.
|
||||
`frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx` now uses
|
||||
a single `INFRASTRUCTURE_PATH` constant (`/settings/infrastructure`) for all
|
||||
install and platform-connection CTAs, replacing the former pair of
|
||||
shared infrastructure path-builder constants for workspace, add-infrastructure,
|
||||
and agent-install CTAs, replacing the former pair of
|
||||
`INFRASTRUCTURE_INSTALL_PATH` and `INFRASTRUCTURE_PLATFORMS_PATH` constants.
|
||||
Installer-owned runtime continuity on persistence-sensitive NAS platforms is
|
||||
also explicit again: `scripts/install.sh` now owns the QNAP bootstrap contract
|
||||
|
||||
@@ -676,7 +676,10 @@ platform/runtime surfaces must stay hidden from the AI-adjacent shell and must
|
||||
not be kept as disabled placeholders. The legacy `/infrastructure` route shell
|
||||
was retired alongside its page wrapper, and `/workloads`, `/storage`,
|
||||
`/recovery`, `/ceph`, `/ai`, and `/operations/*` remain unregistered
|
||||
top-level routes. The AI Chat launcher, Patrol surfaces, and
|
||||
top-level routes. Primary platform tab `settingsRoute` handoffs must also point
|
||||
to the canonical `/settings/infrastructure` workspace rather than retired
|
||||
settings aliases such as `/settings/workloads/docker` or nested
|
||||
`/settings/infrastructure/platforms/*` paths. The AI Chat launcher, Patrol surfaces, and
|
||||
`AssistantHandoffPayload` deep links must use canonical platform or runtime
|
||||
routes (`/proxmox/overview`, `/proxmox/storage`, `/kubernetes/workloads`, etc.)
|
||||
rather than reviving retired Infrastructure or aggregate workspace paths;
|
||||
|
||||
@@ -220,7 +220,7 @@ output or bypass the normal non-admin redaction path.
|
||||
21. `frontend-modern/src/components/Settings/useAPITokenManagerState.ts` shared with `security-privacy`: the API token settings state hook is both a security/privacy control surface and a canonical API payload contract boundary.
|
||||
22. `frontend-modern/src/components/Settings/useInfrastructureConfiguredNodesState.ts` shared with `agent-lifecycle`: the direct-node infrastructure settings state hook is both an agent lifecycle control surface and a shared Proxmox node API contract boundary.
|
||||
23. `frontend-modern/src/components/Settings/useInfrastructureDiscoveryRuntimeState.ts` shared with `agent-lifecycle`: the infrastructure discovery runtime state hook is both an agent lifecycle control surface and a shared discovery/settings API contract boundary.
|
||||
That same shared boundary also owns settings-route polling scope for discovery payloads: the `/api/discover` refresh loop and websocket-backed discovery status hydration may run only while the operator is on the infrastructure connections workspace under `/settings/infrastructure/platforms*`, not on the systems ledger or install workspace.
|
||||
That same shared boundary also owns settings-route polling scope for discovery payloads: the `/api/discover` refresh loop and websocket-backed discovery status hydration may run only while the operator is on the canonical Infrastructure settings workspace at `/settings/infrastructure`, not on retired infrastructure sub-routes.
|
||||
Discovery refreshes and scan results are route-scoped state updates. If the
|
||||
settings route unmounts while a request is in flight, the hook must drop the
|
||||
result and avoid surfacing stale background errors into the next settings
|
||||
|
||||
@@ -512,7 +512,11 @@ or other self-hosted uncapped continuity plans.
|
||||
paid or hosted app shell. Infrastructure is not an equal primary tab in
|
||||
that list. Workloads, Storage, and Recovery are not standalone aggregate
|
||||
workspace tabs; their component surfaces remain reusable inside platform
|
||||
pages via embedded `tableOnly` surfaces. Each platform page must remain
|
||||
pages via embedded `tableOnly` surfaces. Platform primary-tab settings
|
||||
handoffs in `AppLayout.tsx` must target the canonical
|
||||
`/settings/infrastructure` workspace and must not retain retired settings
|
||||
aliases such as `/settings/workloads/docker` or nested
|
||||
`/settings/infrastructure/platforms/*` paths. Each platform page must remain
|
||||
chrome-only:
|
||||
routing plus sub-tab navigation that embeds the canonical
|
||||
`WorkloadsSurface`, `StorageSurface`, `RecoverySurface`, or
|
||||
|
||||
@@ -1401,10 +1401,11 @@ not a replacement status card, CTA band, or page-local nested card.
|
||||
`frontend-modern/src/App.tsx` must land authenticated `/` and `/login`
|
||||
handoffs on the first visible provider/runtime platform in the canonical
|
||||
shell order: Proxmox, Containers, Kubernetes, TrueNAS, vSphere, then Agents
|
||||
only for agent-only estates. Legacy Infrastructure remains route-compatible
|
||||
and may still guide first-time operators into `Add infrastructure`, but it
|
||||
is not the default operational landing surface once provider evidence is
|
||||
present.
|
||||
only for agent-only estates. The retired Infrastructure aggregate route and
|
||||
nested settings infrastructure aliases are not compatibility commitments:
|
||||
first-time operator setup must enter through the canonical Settings →
|
||||
Infrastructure workspace and its query-backed add flow, while provider
|
||||
evidence still owns the operational landing surface.
|
||||
The authenticated app shell's boot-time route preloads must be owned by
|
||||
`frontend-modern/src/routing/routePreload.ts` so top-level cold-tab
|
||||
readiness cannot drift from the route-module preloader. Workloads,
|
||||
@@ -2796,8 +2797,8 @@ shared discovery draft and subnet-validation state,
|
||||
owns infrastructure workspace prop assembly and resource-derived infrastructure
|
||||
read-model shaping for the shell,
|
||||
`frontend-modern/src/components/Settings/settingsNavigationModel.ts` owns
|
||||
settings tab identity, canonical route derivation, legacy alias normalization,
|
||||
and Proxmox agent route metadata. `settingsRouting.ts` and
|
||||
settings tab identity, canonical route derivation, route eligibility, and
|
||||
retired infrastructure/workloads alias rejection. `settingsRouting.ts` and
|
||||
`settingsTypes.ts` remain thin compatibility re-export shims only, so external
|
||||
consumers can bridge to the canonical owner without reintroducing a second
|
||||
settings navigation model. `settingsNavCatalog.ts` owns settings navigation
|
||||
@@ -2809,13 +2810,14 @@ including the route-owned billing focus contract where
|
||||
`/settings/system/billing/plan` is the canonical settings-tab destination,
|
||||
`/settings/system/billing/usage` is a same-tab child state, and legacy billing
|
||||
base/hash links are compatibility inputs rather than primary runtime routes.
|
||||
That same route-sync owner must also preserve Proxmox platform-selection truth
|
||||
across canonical deep links such as
|
||||
`/settings/infrastructure/platforms/proxmox/pbs` and
|
||||
`/settings/infrastructure/platforms/proxmox/pmg`: even though those routes
|
||||
collapse into the shared `infrastructure-operations` tab, the selected
|
||||
platform state must still be derived from the path instead of silently
|
||||
falling back to `pve` on reload or remount.
|
||||
Infrastructure settings no longer has route-level platform-selection state:
|
||||
`/settings/infrastructure` and `/settings/infrastructure?add=<step>` are the
|
||||
only routeable Infrastructure settings entry points. Former nested aliases such
|
||||
as `/settings/infrastructure/install`,
|
||||
`/settings/infrastructure/platforms/proxmox/pbs`,
|
||||
`/settings/infrastructure/api/pve`, and `/settings/workloads/docker` must fail
|
||||
route eligibility instead of being normalized back into the Infrastructure
|
||||
workspace.
|
||||
That same settings access boundary must keep route eligibility separate from
|
||||
sidebar visibility. Panel-owned feature gates such as Relay, Reporting, RBAC,
|
||||
Audit Log, and Audit Webhooks may be hidden from the navigation on Community
|
||||
@@ -3533,17 +3535,16 @@ The settings navigation model now exposes a single `infrastructure-systems`
|
||||
sidebar entry for the infrastructure settings area. The former
|
||||
`infrastructure-connections` and `infrastructure-install` entries have been
|
||||
removed from `SettingsTab`, `settingsNavCatalog.ts`, `settingsPanelRegistry.ts`,
|
||||
and `settingsNavigationModel.ts`. All canonical redirects and tab-derivation
|
||||
logic that previously mapped to those two entries now collapse to
|
||||
`infrastructure-systems`. No future additions to the settings nav may restore
|
||||
and `settingsNavigationModel.ts`. No future additions to the settings nav may restore
|
||||
`infrastructure-connections` or `infrastructure-install` as independent tab
|
||||
identifiers; panel routing within the infrastructure area must use
|
||||
`InfrastructurePanelStep` in-page state instead of URL sub-routes.
|
||||
`frontend-modern/src/components/Settings/settingsNavigationModel.ts` now uses
|
||||
the normalised (not canonical) path when resolving Proxmox agent and path
|
||||
checks so that deep links such as `/settings/infrastructure/platforms/proxmox/pbs`
|
||||
resolve to the correct agent before the canonical-redirect fires, rather than
|
||||
after it has already collapsed the path.
|
||||
`frontend-modern/src/components/Settings/settingsNavigationModel.ts` owns the
|
||||
explicit routeability check that rejects retired infrastructure/workloads
|
||||
aliases before the settings shell mounts. `useSettingsNavigation.ts` may
|
||||
redirect `/settings` and still canonicalize current settings destinations, but
|
||||
it must not translate removed infrastructure subpaths into onboarding queries or
|
||||
derive Proxmox platform state from those paths.
|
||||
The shared frontend source/platform vocabulary now also includes
|
||||
`availability` as an agentless infrastructure source and `network-endpoint` as
|
||||
the canonical resource projection. Picker cards, source labels, badges, and
|
||||
|
||||
@@ -718,7 +718,6 @@
|
||||
"frontend-modern/src/components/Settings/NodeModalMonitoringSection.tsx",
|
||||
"frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx",
|
||||
"frontend-modern/src/components/Settings/NodeModalStatusFooter.tsx",
|
||||
"frontend-modern/src/components/Settings/platformConnectionsModel.ts",
|
||||
"frontend-modern/src/components/Settings/proxmoxSettingsModel.ts",
|
||||
"frontend-modern/src/components/Settings/SettingsSectionNav.tsx",
|
||||
"frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts",
|
||||
@@ -1054,7 +1053,6 @@
|
||||
"frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx",
|
||||
"frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts",
|
||||
"frontend-modern/src/components/Settings/MonitoredSystemImpactPreview.tsx",
|
||||
"frontend-modern/src/components/Settings/platformConnectionsModel.ts",
|
||||
"frontend-modern/src/components/Settings/proxmoxSettingsModel.ts",
|
||||
"frontend-modern/src/components/Settings/SettingsSectionNav.tsx",
|
||||
"frontend-modern/src/components/Settings/useConnectionRowActions.ts",
|
||||
|
||||
@@ -50,6 +50,7 @@ import { isPro } from '@/stores/licenseCommercial';
|
||||
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
|
||||
import { AI_CHAT_LAUNCHER_ARIA_LABEL, getAIChatLauncherTitle } from '@/utils/aiChatPresentation';
|
||||
import type { AppConnectionStatus } from '@/useAppRuntimeState';
|
||||
import { buildInfrastructureWorkspacePath } from '@/components/Settings/infrastructureWorkspaceModel';
|
||||
|
||||
const ROOT_PROXMOX_PATH = buildProxmoxPath();
|
||||
const ROOT_DOCKER_PATH = buildDockerPath();
|
||||
@@ -57,6 +58,7 @@ const ROOT_KUBERNETES_PATH = buildKubernetesPath();
|
||||
const ROOT_TRUENAS_PATH = buildTrueNASPath();
|
||||
const ROOT_VMWARE_PATH = buildVmwarePath();
|
||||
const ROOT_AGENTS_PATH = buildAgentsPath();
|
||||
const ROOT_INFRASTRUCTURE_SETTINGS_PATH = buildInfrastructureWorkspacePath();
|
||||
const ROOT_ALERTS_PATH = '/alerts';
|
||||
const NAV_TAB_ICON_CLASS = 'w-4 h-4 shrink-0';
|
||||
const AI_CHAT_LAUNCHER_BUTTON_CLASS =
|
||||
@@ -332,7 +334,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
id: 'proxmox',
|
||||
label: 'Proxmox',
|
||||
route: ROOT_PROXMOX_PATH,
|
||||
settingsRoute: '/settings/infrastructure/platforms/proxmox/pve',
|
||||
settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH,
|
||||
tooltip: 'Proxmox VE, Backup Server, Mail Gateway, storage, backups, and guests',
|
||||
enabled: isVisible('proxmox'),
|
||||
live: isVisible('proxmox'),
|
||||
@@ -343,7 +345,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
id: 'docker',
|
||||
label: 'Containers',
|
||||
route: ROOT_DOCKER_PATH,
|
||||
settingsRoute: '/settings/workloads/docker',
|
||||
settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH,
|
||||
tooltip: 'Docker / Podman runtime lens: hosts, containers, and Swarm services',
|
||||
enabled: isVisible('docker'),
|
||||
live: isVisible('docker'),
|
||||
@@ -354,7 +356,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
id: 'kubernetes',
|
||||
label: 'Kubernetes',
|
||||
route: ROOT_KUBERNETES_PATH,
|
||||
settingsRoute: '/settings',
|
||||
settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH,
|
||||
tooltip: 'Kubernetes clusters, nodes, pods, deployments, and services',
|
||||
enabled: isVisible('kubernetes'),
|
||||
live: isVisible('kubernetes'),
|
||||
@@ -365,7 +367,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
id: 'truenas',
|
||||
label: 'TrueNAS',
|
||||
route: ROOT_TRUENAS_PATH,
|
||||
settingsRoute: '/settings/infrastructure',
|
||||
settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH,
|
||||
tooltip: 'TrueNAS hosts, storage, and apps',
|
||||
enabled: isVisible('truenas'),
|
||||
live: isVisible('truenas'),
|
||||
@@ -376,7 +378,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
id: 'vmware',
|
||||
label: 'vSphere',
|
||||
route: ROOT_VMWARE_PATH,
|
||||
settingsRoute: '/settings/infrastructure',
|
||||
settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH,
|
||||
tooltip: 'VMware vSphere hosts, virtual machines, datastores, and networks',
|
||||
enabled: isVisible('vmware'),
|
||||
live: isVisible('vmware'),
|
||||
@@ -387,7 +389,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
id: 'agents',
|
||||
label: 'Agents',
|
||||
route: ROOT_AGENTS_PATH,
|
||||
settingsRoute: '/settings/infrastructure',
|
||||
settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH,
|
||||
tooltip: 'Standalone Pulse Agent machines, OS telemetry, storage, and command eligibility',
|
||||
enabled: isVisible('agents'),
|
||||
live: isVisible('agents'),
|
||||
|
||||
@@ -46,7 +46,9 @@ describe('App architecture', () => {
|
||||
expect(appSource).not.toContain('const WorkloadsPage = lazy(');
|
||||
expect(appSource).not.toContain('const StoragePage = lazy(');
|
||||
expect(appSource).not.toContain('const RecoveryPage = lazy(');
|
||||
expect(appSource).not.toContain("import('./features/infrastructure/InfrastructurePageSurface')");
|
||||
expect(appSource).not.toContain(
|
||||
"import('./features/infrastructure/InfrastructurePageSurface')",
|
||||
);
|
||||
expect(appSource).not.toContain('component={InfrastructurePage}');
|
||||
expect(appSource).not.toContain('INFRASTRUCTURE_PATH');
|
||||
expect(appSource).not.toContain('<Route path={WORKLOADS_PATH}');
|
||||
@@ -110,7 +112,7 @@ describe('App architecture', () => {
|
||||
expect(appLayoutSource).not.toContain("id: 'workloads',");
|
||||
expect(appLayoutSource).not.toContain("id: 'storage',");
|
||||
expect(appLayoutSource).not.toContain("id: 'recovery',");
|
||||
expect(appLayoutSource).not.toContain("aria-label=\"Workspaces\"");
|
||||
expect(appLayoutSource).not.toContain('aria-label="Workspaces"');
|
||||
expect(appLayoutSource).not.toContain('buildStorageRecoveryTabSpecs(');
|
||||
expect(appSource).not.toContain('DashboardPage');
|
||||
expect(headerAuditSource).not.toContain("['src/pages/Dashboard.tsx', 'PageHeader']");
|
||||
@@ -183,6 +185,15 @@ describe('App architecture', () => {
|
||||
expect(appLayoutSource).toContain(
|
||||
"import { dialogStackHasBlockingDialog } from '@/components/shared/useDialogState';",
|
||||
);
|
||||
expect(appLayoutSource).toContain(
|
||||
"import { buildInfrastructureWorkspacePath } from '@/components/Settings/infrastructureWorkspaceModel';",
|
||||
);
|
||||
expect(appLayoutSource).toContain(
|
||||
'const ROOT_INFRASTRUCTURE_SETTINGS_PATH = buildInfrastructureWorkspacePath();',
|
||||
);
|
||||
expect(appLayoutSource).toContain('settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH');
|
||||
expect(appLayoutSource).not.toContain("settingsRoute: '/settings/workloads");
|
||||
expect(appLayoutSource).not.toContain("settingsRoute: '/settings/infrastructure/platforms");
|
||||
expect(appLayoutSource).toContain('<OrgSwitcher');
|
||||
expect(appLayoutSource).toContain('const status = () => props.connectionStatus();');
|
||||
expect(appLayoutSource).toContain(
|
||||
|
||||
@@ -17,6 +17,8 @@ import { useSettingsSystemPanels } from './useSettingsSystemPanels';
|
||||
import { useSystemSettingsState } from './useSystemSettingsState';
|
||||
import { useSettingsNavigation } from './useSettingsNavigation';
|
||||
import { getSettingsLoadingState } from '@/utils/settingsShellPresentation';
|
||||
import { isRouteableSettingsPath } from './settingsNavigationModel';
|
||||
import NotFound from '@/pages/NotFound';
|
||||
|
||||
import { getRuntimeLimit, loadRuntimeCapabilities } from '@/stores/license';
|
||||
import { isPro } from '@/stores/licenseCommercial';
|
||||
@@ -33,7 +35,7 @@ const SettingsPanelLoadingFallback = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const Settings: Component<SettingsProps> = (props) => {
|
||||
const SettingsWorkspace: Component<SettingsProps> = (props) => {
|
||||
const { state, connected: _connected } = useWebSocket();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -219,4 +221,15 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Settings: Component<SettingsProps> = (props) => {
|
||||
const location = useLocation();
|
||||
const isRouteableSettingsRoute = createMemo(() => isRouteableSettingsPath(location.pathname));
|
||||
|
||||
return (
|
||||
<Show when={isRouteableSettingsRoute()} fallback={<NotFound />}>
|
||||
<SettingsWorkspace {...props} />
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
+4
@@ -166,6 +166,10 @@ describe('infrastructure operations model', () => {
|
||||
expect(hasMachineInstallActions(row)).toBe(false);
|
||||
expect(getCapabilitySurfaceLabel('availability')).toBe('Availability data');
|
||||
expect(getCapabilityManagementPath('availability')).toBe('/settings/infrastructure');
|
||||
expect(getCapabilityManagementPath('proxmox')).toBe('/settings/infrastructure');
|
||||
expect(getCapabilityManagementPath('pbs')).toBe('/settings/infrastructure');
|
||||
expect(getCapabilityManagementPath('pmg')).toBe('/settings/infrastructure');
|
||||
expect(getCapabilityManagementPath('truenas')).toBe('/settings/infrastructure');
|
||||
expect(getPlatformConnectionsViewForCapability('availability')).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
+7
-17
@@ -3,7 +3,6 @@ import {
|
||||
buildInfrastructureOnboardingPath,
|
||||
buildInfrastructureWorkspacePath,
|
||||
deriveAddStepFromLocation,
|
||||
deriveAddStepFromLegacyPath,
|
||||
} from '../infrastructureWorkspaceModel';
|
||||
|
||||
describe('infrastructureWorkspaceModel', () => {
|
||||
@@ -20,29 +19,20 @@ describe('infrastructureWorkspaceModel', () => {
|
||||
expect(buildInfrastructureOnboardingPath('truenas')).toBe(
|
||||
'/settings/infrastructure?add=truenas',
|
||||
);
|
||||
expect(buildInfrastructureOnboardingPath('unraid')).toBe(
|
||||
'/settings/infrastructure?add=unraid',
|
||||
);
|
||||
expect(buildInfrastructureOnboardingPath('docker')).toBe(
|
||||
'/settings/infrastructure?add=docker',
|
||||
);
|
||||
expect(buildInfrastructureOnboardingPath('vmware')).toBe(
|
||||
'/settings/infrastructure?add=vmware',
|
||||
);
|
||||
expect(buildInfrastructureOnboardingPath('unraid')).toBe('/settings/infrastructure?add=unraid');
|
||||
expect(buildInfrastructureOnboardingPath('docker')).toBe('/settings/infrastructure?add=docker');
|
||||
expect(buildInfrastructureOnboardingPath('vmware')).toBe('/settings/infrastructure?add=vmware');
|
||||
});
|
||||
|
||||
it('derives add steps from onboarding deep links', () => {
|
||||
expect(deriveAddStepFromLegacyPath('/settings/infrastructure/install')).toBe('linux-host');
|
||||
expect(deriveAddStepFromLegacyPath('/settings/infrastructure/platforms')).toBe('pick');
|
||||
expect(deriveAddStepFromLegacyPath('/settings/infrastructure/platforms/truenas')).toBeNull();
|
||||
it('derives add steps only from the canonical infrastructure workspace query', () => {
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure', '?add=agent')).toBe('agent');
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure', '?add=pick')).toBe('pick');
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure', '?add=truenas')).toBe(
|
||||
'truenas',
|
||||
);
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure', '?add=truenas')).toBe('truenas');
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure', '?add=unraid')).toBe('unraid');
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure', '?add=kubernetes')).toBe(
|
||||
'kubernetes',
|
||||
);
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure/install', '')).toBeNull();
|
||||
expect(deriveAddStepFromLocation('/settings/infrastructure/platforms', '?add=pick')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('settings architecture guardrails', () => {
|
||||
expect(settingsDialogsSource).toContain('export const SettingsDialogs');
|
||||
});
|
||||
|
||||
it('keeps infrastructure onboarding route-backed under the shared settings shell', () => {
|
||||
it('keeps infrastructure onboarding query-backed under the shared settings shell', () => {
|
||||
expect(settingsHeaderMetaSource).toContain("'infrastructure-systems': {");
|
||||
expect(settingsHeaderMetaSource).toContain(
|
||||
'Add, discover, and verify the infrastructure Pulse monitors.',
|
||||
@@ -105,10 +105,10 @@ describe('settings architecture guardrails', () => {
|
||||
'Manage organization invitations, member roles, and ownership transfers.',
|
||||
);
|
||||
|
||||
expect(settingsNavigationHookSource).toContain('deriveAddStepFromLegacyPath(path)');
|
||||
expect(settingsNavigationHookSource).toContain(
|
||||
'navigate(buildInfrastructureOnboardingPath(infrastructureOnboardingStep), {',
|
||||
);
|
||||
expect(settingsSource).toContain("import NotFound from '@/pages/NotFound';");
|
||||
expect(settingsSource).toContain('isRouteableSettingsPath(location.pathname)');
|
||||
expect(settingsNavigationHookSource).not.toContain('deriveAddStepFromLegacyPath');
|
||||
expect(settingsNavigationHookSource).not.toContain('buildInfrastructureOnboardingPath');
|
||||
expect(settingsNavigationHookSource).toContain(
|
||||
'navigate(buildInfrastructureWorkspacePath(), {',
|
||||
);
|
||||
@@ -120,9 +120,7 @@ describe('settings architecture guardrails', () => {
|
||||
expect(infrastructureWorkspaceModelSource).toContain(
|
||||
'export function buildInfrastructureOnboardingPath(',
|
||||
);
|
||||
expect(infrastructureWorkspaceModelSource).toContain(
|
||||
'export function deriveAddStepFromLegacyPath(',
|
||||
);
|
||||
expect(infrastructureWorkspaceModelSource).not.toContain('deriveAddStepFromLegacyPath');
|
||||
expect(infrastructureWorkspaceModelSource).toContain(
|
||||
'export function deriveAddStepFromSearch(',
|
||||
);
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
buildLegacyOperationsSettingsPath,
|
||||
agentKeyFromPlatformType,
|
||||
DEFAULT_SETTINGS_TAB,
|
||||
deriveAgentFromPath,
|
||||
deriveTabFromPath,
|
||||
deriveTabFromQuery,
|
||||
isRetiredSettingsCompatibilityPath,
|
||||
isRouteableSettingsPath,
|
||||
resolveCanonicalSettingsPath,
|
||||
settingsAgentLabel,
|
||||
settingsAgentNodeLabel,
|
||||
settingsAgentPath,
|
||||
settingsAgentPlatformType,
|
||||
settingsTabPath,
|
||||
type SettingsTab,
|
||||
@@ -66,12 +66,8 @@ describe('settingsNavigationModel', () => {
|
||||
|
||||
it('resolves only canonical settings paths', () => {
|
||||
expect(resolveCanonicalSettingsPath('/settings')).toBe('/settings/infrastructure');
|
||||
expect(resolveCanonicalSettingsPath('/settings/workloads')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/workloads/docker')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/workloads')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/workloads/docker')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/support')).toBe('/settings/support/diagnostics');
|
||||
expect(resolveCanonicalSettingsPath('/settings/system-updates')).toBe(
|
||||
'/settings/system-updates',
|
||||
@@ -79,29 +75,13 @@ describe('settingsNavigationModel', () => {
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
// All infrastructure sub-paths collapse to the single workspace.
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/install')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/platforms')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/platforms/truenas')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/operations')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
// Legacy proxmox paths redirect to the workspace.
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/api')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/api/pve')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/proxmox')).toBe(
|
||||
'/settings/infrastructure',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/install')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/platforms')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/platforms/truenas')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/operations')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/api')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/api/pve')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/proxmox')).toBeNull();
|
||||
expect(resolveCanonicalSettingsPath('/settings/integrations/api')).toBe(
|
||||
'/settings/security/api',
|
||||
);
|
||||
@@ -123,6 +103,31 @@ describe('settingsNavigationModel', () => {
|
||||
expect(resolveCanonicalSettingsPath('/not-settings')).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps retired infrastructure settings aliases out of routeable settings paths', () => {
|
||||
const retiredPaths = [
|
||||
'/settings/workloads',
|
||||
'/settings/workloads/docker',
|
||||
'/settings/workloads/docker/agents',
|
||||
'/settings/infrastructure/install',
|
||||
'/settings/infrastructure/platforms',
|
||||
'/settings/infrastructure/platforms/proxmox/pve',
|
||||
'/settings/infrastructure/api/pve',
|
||||
'/settings/infrastructure/proxmox',
|
||||
'/settings/infrastructure/truenas',
|
||||
'/settings/infrastructure/vmware',
|
||||
];
|
||||
|
||||
for (const path of retiredPaths) {
|
||||
expect(isRetiredSettingsCompatibilityPath(path)).toBe(true);
|
||||
expect(isRouteableSettingsPath(path)).toBe(false);
|
||||
}
|
||||
|
||||
expect(isRouteableSettingsPath('/settings')).toBe(true);
|
||||
expect(isRouteableSettingsPath('/settings/infrastructure')).toBe(true);
|
||||
expect(isRouteableSettingsPath('/settings/security/api')).toBe(true);
|
||||
expect(isRouteableSettingsPath('/settings/unknown')).toBe(false);
|
||||
});
|
||||
|
||||
it('maps organization routing contracts', () => {
|
||||
const organizationCases: Array<[string, SettingsTab]> = [
|
||||
['/settings/organization', 'organization-overview'],
|
||||
@@ -143,13 +148,6 @@ describe('settingsNavigationModel', () => {
|
||||
it('maps query deep-links contract values', () => {
|
||||
const queryCases: Array<[string, SettingsTab | null]> = [
|
||||
['?tab=infrastructure', 'infrastructure-systems'],
|
||||
['?tab=agents', 'infrastructure-systems'],
|
||||
['?tab=workloads', 'infrastructure-systems'],
|
||||
['?tab=proxmox', 'infrastructure-systems'],
|
||||
['?tab=connections', 'infrastructure-systems'],
|
||||
['?tab=platforms', 'infrastructure-systems'],
|
||||
['?tab=install', 'infrastructure-systems'],
|
||||
['?tab=docker', 'infrastructure-systems'],
|
||||
['?tab=system-ai', 'system-ai'],
|
||||
['?tab=system-relay', 'system-relay'],
|
||||
['?tab=system-pro', 'system-billing'],
|
||||
@@ -163,6 +161,9 @@ describe('settingsNavigationModel', () => {
|
||||
['?tab=security-overview', 'security-overview'],
|
||||
['?tab=data-handling', 'security-data-handling'],
|
||||
['?tab=resource-privacy', 'security-data-handling'],
|
||||
['?tab=workloads', null],
|
||||
['?tab=install', null],
|
||||
['?tab=docker', null],
|
||||
['?tab=unknown', null],
|
||||
];
|
||||
for (const [query, expectedTab] of queryCases) {
|
||||
@@ -198,36 +199,15 @@ describe('settingsNavigationModel', () => {
|
||||
expect(isTabLocked('infrastructure-systems', hasFeatures([]), () => true)).toBe(false);
|
||||
});
|
||||
|
||||
it('maps deriveAgentFromPath contracts for canonical infrastructure routes', () => {
|
||||
const agentCases: Array<[string, 'pve' | 'pbs' | 'pmg' | null]> = [
|
||||
// Legacy api/* paths now resolve to the workspace base — no agent derived.
|
||||
['/settings/infrastructure/api/pve', null],
|
||||
['/settings/infrastructure/api/pbs', null],
|
||||
['/settings/infrastructure/api/pmg', null],
|
||||
// Canonical platform paths still resolve correctly.
|
||||
['/settings/infrastructure/platforms/proxmox/pve', 'pve'],
|
||||
['/settings/infrastructure/platforms/proxmox/pbs', 'pbs'],
|
||||
['/settings/infrastructure/platforms/proxmox/pmg', 'pmg'],
|
||||
['/settings/infrastructure', null],
|
||||
['/settings/infrastructure/operations', null],
|
||||
];
|
||||
for (const [path, expectedAgent] of agentCases) {
|
||||
expect(deriveAgentFromPath(path)).toBe(expectedAgent);
|
||||
}
|
||||
});
|
||||
|
||||
it('maps settings agent keys to canonical platform types, labels, and paths', () => {
|
||||
expect(settingsAgentPath('pve')).toBe('/settings/infrastructure/platforms/proxmox/pve');
|
||||
it('maps settings agent keys to platform types and labels', () => {
|
||||
expect(settingsAgentPlatformType('pve')).toBe('proxmox-pve');
|
||||
expect(settingsAgentLabel('pve')).toBe('Proxmox VE');
|
||||
expect(settingsAgentNodeLabel('pve')).toBe('Proxmox VE node');
|
||||
|
||||
expect(settingsAgentPath('pbs')).toBe('/settings/infrastructure/platforms/proxmox/pbs');
|
||||
expect(settingsAgentPlatformType('pbs')).toBe('proxmox-pbs');
|
||||
expect(settingsAgentLabel('pbs')).toBe('Proxmox Backup Server');
|
||||
expect(settingsAgentNodeLabel('pbs')).toBe('Proxmox Backup Server');
|
||||
|
||||
expect(settingsAgentPath('pmg')).toBe('/settings/infrastructure/platforms/proxmox/pmg');
|
||||
expect(settingsAgentPlatformType('pmg')).toBe('proxmox-pmg');
|
||||
expect(settingsAgentLabel('pmg')).toBe('Proxmox Mail Gateway');
|
||||
expect(settingsAgentNodeLabel('pmg')).toBe('Proxmox Mail Gateway');
|
||||
@@ -243,18 +223,6 @@ describe('settingsNavigationModel', () => {
|
||||
expect(agentKeyFromPlatformType('agent')).toBeNull();
|
||||
});
|
||||
|
||||
it('treats proxmox deep links as infrastructure aliases', () => {
|
||||
expect(deriveTabFromPath('/settings/infrastructure/platforms')).toBe('infrastructure-systems');
|
||||
expect(deriveTabFromPath('/settings/infrastructure/platforms/proxmox')).toBe(
|
||||
'infrastructure-systems',
|
||||
);
|
||||
expect(deriveTabFromPath('/settings/infrastructure/platforms/proxmox/pve')).toBe(
|
||||
'infrastructure-systems',
|
||||
);
|
||||
expect(deriveTabFromPath('/settings/infrastructure/api/pve')).toBe('infrastructure-systems');
|
||||
expect(deriveTabFromPath('/settings/infrastructure/install')).toBe('infrastructure-systems');
|
||||
});
|
||||
|
||||
it('maps support and legacy settings operations routes back to support tabs', () => {
|
||||
expect(deriveTabFromPath('/settings/support/diagnostics')).toBe('support-diagnostics');
|
||||
expect(deriveTabFromPath('/settings/support/reporting')).toBe('support-reporting');
|
||||
|
||||
@@ -80,52 +80,28 @@ describe('useSettingsNavigation', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('syncs the selected proxmox agent from canonical deep links on initial load', async () => {
|
||||
it('does not derive selected agents from retired infrastructure deep links', async () => {
|
||||
renderHarness('/settings/infrastructure/platforms/proxmox/pbs');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('selected-agent')).toHaveTextContent('pbs');
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults the selected proxmox agent to pve on the base proxmox route', async () => {
|
||||
renderHarness('/settings/infrastructure/platforms/proxmox');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('selected-agent')).toHaveTextContent('pve');
|
||||
});
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('translates the legacy install route into the canonical onboarding query', async () => {
|
||||
it('does not translate retired infrastructure setup paths', async () => {
|
||||
renderHarness('/settings/infrastructure/install');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=linux-host', {
|
||||
replace: true,
|
||||
scroll: false,
|
||||
});
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('translates the legacy platform chooser route into the canonical onboarding query', async () => {
|
||||
renderHarness('/settings/infrastructure/platforms');
|
||||
it('does not translate retired workloads settings paths', async () => {
|
||||
renderHarness('/settings/workloads/docker');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=pick', {
|
||||
replace: true,
|
||||
scroll: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps legacy platform-management paths out of onboarding mode', async () => {
|
||||
renderHarness('/settings/infrastructure/platforms/truenas');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure', {
|
||||
replace: true,
|
||||
scroll: false,
|
||||
});
|
||||
expect(navigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { JSX } from 'solid-js';
|
||||
import type { ConnectedInfrastructureItem, ConnectedInfrastructureSurface } from '@/types/api';
|
||||
import { getAgentCapabilityLabel, type AgentCapability } from '@/utils/agentCapabilityPresentation';
|
||||
import { getSourcePlatformLabel } from '@/utils/sourcePlatforms';
|
||||
import { type PlatformConnectionsView } from './platformConnectionsModel';
|
||||
import { buildInfrastructureWorkspacePath } from './infrastructureWorkspaceModel';
|
||||
|
||||
export const TOKEN_PLACEHOLDER = '<api-token>';
|
||||
|
||||
export type PlatformConnectionsView = 'proxmox' | 'truenas' | 'vmware';
|
||||
export type AgentPlatform = 'linux' | 'macos' | 'freebsd' | 'windows';
|
||||
export type UnifiedAgentStatus = 'active' | 'removed';
|
||||
export type ScopeCategory = 'default' | 'profile' | 'ai-managed' | 'na';
|
||||
|
||||
@@ -15,16 +15,6 @@ export type InfrastructurePanelStep = 'pick' | InfrastructureAddStep;
|
||||
|
||||
const INFRASTRUCTURE_BASE_PATH = '/settings/infrastructure';
|
||||
export const INFRASTRUCTURE_ADD_QUERY_PARAM = 'add';
|
||||
const LEGACY_INSTALL_PATH = `${INFRASTRUCTURE_BASE_PATH}/install`;
|
||||
const LEGACY_PLATFORMS_PATH = `${INFRASTRUCTURE_BASE_PATH}/platforms`;
|
||||
|
||||
function matchesExactPath(pathname: string, expected: string): boolean {
|
||||
return pathname === expected || pathname === `${expected}/`;
|
||||
}
|
||||
|
||||
function matchesPathPrefix(pathname: string, expected: string): boolean {
|
||||
return pathname === expected || pathname.startsWith(`${expected}/`);
|
||||
}
|
||||
|
||||
export function normalizeInfrastructurePanelStep(
|
||||
value: string | null | undefined,
|
||||
@@ -59,12 +49,6 @@ export function buildInfrastructureOnboardingPath(step: InfrastructurePanelStep
|
||||
return `${INFRASTRUCTURE_BASE_PATH}?${params.toString()}`;
|
||||
}
|
||||
|
||||
export function deriveAddStepFromLegacyPath(pathname: string): InfrastructurePanelStep | null {
|
||||
if (matchesPathPrefix(pathname, LEGACY_INSTALL_PATH)) return 'linux-host';
|
||||
if (matchesExactPath(pathname, LEGACY_PLATFORMS_PATH)) return 'pick';
|
||||
return null;
|
||||
}
|
||||
|
||||
export function deriveAddStepFromSearch(search: string): InfrastructurePanelStep | null {
|
||||
const params = new URLSearchParams(search);
|
||||
return normalizeInfrastructurePanelStep(params.get(INFRASTRUCTURE_ADD_QUERY_PARAM));
|
||||
@@ -74,15 +58,9 @@ export function deriveAddStepFromLocation(
|
||||
pathname: string,
|
||||
search: string,
|
||||
): InfrastructurePanelStep | null {
|
||||
if (
|
||||
pathname === INFRASTRUCTURE_BASE_PATH ||
|
||||
pathname.startsWith(`${INFRASTRUCTURE_BASE_PATH}/`)
|
||||
) {
|
||||
const stepFromQuery = deriveAddStepFromSearch(search);
|
||||
if (stepFromQuery) {
|
||||
return stepFromQuery;
|
||||
}
|
||||
if (pathname !== INFRASTRUCTURE_BASE_PATH && pathname !== `${INFRASTRUCTURE_BASE_PATH}/`) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return deriveAddStepFromLegacyPath(pathname);
|
||||
return deriveAddStepFromSearch(search);
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
export type PlatformConnectionsView = 'proxmox' | 'truenas' | 'vmware';
|
||||
|
||||
export interface PlatformConnectionsTabDefinition {
|
||||
id: PlatformConnectionsView;
|
||||
label: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export const PLATFORM_CONNECTIONS_PREFIX = '/settings/infrastructure/platforms';
|
||||
const PLATFORM_CONNECTIONS_PROXMOX_PREFIX = `${PLATFORM_CONNECTIONS_PREFIX}/proxmox`;
|
||||
const PLATFORM_CONNECTIONS_TRUENAS_PREFIX = `${PLATFORM_CONNECTIONS_PREFIX}/truenas`;
|
||||
const PLATFORM_CONNECTIONS_VMWARE_PREFIX = `${PLATFORM_CONNECTIONS_PREFIX}/vmware`;
|
||||
const LEGACY_PROXMOX_PREFIX = '/settings/infrastructure/proxmox';
|
||||
const LEGACY_PROXMOX_API_PREFIX = '/settings/infrastructure/api';
|
||||
const LEGACY_TRUENAS_PREFIX = '/settings/infrastructure/truenas';
|
||||
const LEGACY_VMWARE_PREFIX = '/settings/infrastructure/vmware';
|
||||
|
||||
export const PLATFORM_CONNECTIONS_TABS: readonly PlatformConnectionsTabDefinition[] = [
|
||||
{
|
||||
id: 'proxmox',
|
||||
label: 'Proxmox',
|
||||
path: PLATFORM_CONNECTIONS_PROXMOX_PREFIX,
|
||||
},
|
||||
{
|
||||
id: 'truenas',
|
||||
label: 'TrueNAS',
|
||||
path: PLATFORM_CONNECTIONS_TRUENAS_PREFIX,
|
||||
},
|
||||
{
|
||||
id: 'vmware',
|
||||
label: 'VMware',
|
||||
path: PLATFORM_CONNECTIONS_VMWARE_PREFIX,
|
||||
},
|
||||
];
|
||||
|
||||
export function getPlatformConnectionsViewFromPath(pathname: string): PlatformConnectionsView {
|
||||
if (pathname.startsWith(PLATFORM_CONNECTIONS_VMWARE_PREFIX)) {
|
||||
return 'vmware';
|
||||
}
|
||||
if (pathname.startsWith(PLATFORM_CONNECTIONS_TRUENAS_PREFIX)) {
|
||||
return 'truenas';
|
||||
}
|
||||
if (pathname.startsWith(LEGACY_VMWARE_PREFIX)) {
|
||||
return 'vmware';
|
||||
}
|
||||
if (pathname.startsWith(LEGACY_TRUENAS_PREFIX)) {
|
||||
return 'truenas';
|
||||
}
|
||||
if (pathname.startsWith(PLATFORM_CONNECTIONS_PREFIX)) {
|
||||
return 'proxmox';
|
||||
}
|
||||
if (
|
||||
pathname.startsWith(LEGACY_PROXMOX_PREFIX) ||
|
||||
pathname.startsWith(LEGACY_PROXMOX_API_PREFIX)
|
||||
) {
|
||||
return 'proxmox';
|
||||
}
|
||||
return 'proxmox';
|
||||
}
|
||||
|
||||
export function buildPlatformConnectionsPath(view: PlatformConnectionsView): string {
|
||||
return (
|
||||
PLATFORM_CONNECTIONS_TABS.find((tab) => tab.id === view)?.path ??
|
||||
PLATFORM_CONNECTIONS_PROXMOX_PREFIX
|
||||
);
|
||||
}
|
||||
@@ -86,15 +86,11 @@ export type SettingsHeaderMetaMap = Record<SettingsTab, SettingsHeaderMeta>;
|
||||
// Default landing tab for /settings when no deep-link tab is provided.
|
||||
export const DEFAULT_SETTINGS_TAB: SettingsTab = 'infrastructure-systems';
|
||||
const INFRASTRUCTURE_SYSTEMS_PREFIX = '/settings/infrastructure';
|
||||
const LEGACY_AGENTS_PREFIX = '/settings/workloads';
|
||||
const LEGACY_DOCKER_PREFIX = '/settings/workloads/docker';
|
||||
const RETIRED_SETTINGS_WORKLOADS_PREFIX = '/settings/workloads';
|
||||
const SUPPORT_PREFIX = '/settings/support';
|
||||
const SUPPORT_DIAGNOSTICS_PREFIX = `${SUPPORT_PREFIX}/diagnostics`;
|
||||
const SUPPORT_REPORTING_PREFIX = `${SUPPORT_PREFIX}/reporting`;
|
||||
const SUPPORT_LOGS_PREFIX = `${SUPPORT_PREFIX}/logs`;
|
||||
const PROXMOX_PREFIX = `${INFRASTRUCTURE_SYSTEMS_PREFIX}/platforms/proxmox`;
|
||||
const LEGACY_PROXMOX_PREFIX = '/settings/infrastructure/proxmox';
|
||||
const LEGACY_PROXMOX_API_PREFIX = '/settings/infrastructure/api';
|
||||
const LEGACY_INTEGRATIONS_API_PREFIX = '/settings/integrations/api';
|
||||
const LEGACY_SETTINGS_OPERATIONS_PREFIX = '/settings/operations';
|
||||
const SECURITY_API_PREFIX = '/settings/security/api';
|
||||
@@ -104,26 +100,22 @@ const LEGACY_SYSTEM_PRO_PREFIX = '/settings/system-pro';
|
||||
const PROXMOX_AGENT_META: Record<
|
||||
AgentKey,
|
||||
{
|
||||
path: string;
|
||||
platformType: ProxmoxPlatformType;
|
||||
label: string;
|
||||
nodeLabel: string;
|
||||
}
|
||||
> = {
|
||||
pve: {
|
||||
path: `${PROXMOX_PREFIX}/pve`,
|
||||
platformType: 'proxmox-pve',
|
||||
label: 'Proxmox VE',
|
||||
nodeLabel: 'Proxmox VE node',
|
||||
},
|
||||
pbs: {
|
||||
path: `${PROXMOX_PREFIX}/pbs`,
|
||||
platformType: 'proxmox-pbs',
|
||||
label: 'Proxmox Backup Server',
|
||||
nodeLabel: 'Proxmox Backup Server',
|
||||
},
|
||||
pmg: {
|
||||
path: `${PROXMOX_PREFIX}/pmg`,
|
||||
platformType: 'proxmox-pmg',
|
||||
label: 'Proxmox Mail Gateway',
|
||||
nodeLabel: 'Proxmox Mail Gateway',
|
||||
@@ -139,22 +131,22 @@ const normalizeSettingsPath = (path: string): string => {
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
export function isRetiredSettingsCompatibilityPath(path: string): boolean {
|
||||
const normalizedPath = normalizeSettingsPath(path);
|
||||
return (
|
||||
normalizedPath === RETIRED_SETTINGS_WORKLOADS_PREFIX ||
|
||||
normalizedPath.startsWith(`${RETIRED_SETTINGS_WORKLOADS_PREFIX}/`) ||
|
||||
normalizedPath.startsWith(`${INFRASTRUCTURE_SYSTEMS_PREFIX}/`)
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveCanonicalSettingsPath(path: string): string | null {
|
||||
const normalizedPath = normalizeSettingsPath(path);
|
||||
if (!normalizedPath.startsWith('/settings')) return null;
|
||||
if (normalizedPath !== '/settings' && !normalizedPath.startsWith('/settings/')) return null;
|
||||
if (isRetiredSettingsCompatibilityPath(normalizedPath)) return null;
|
||||
if (normalizedPath === '/settings') {
|
||||
return settingsTabPath(DEFAULT_SETTINGS_TAB);
|
||||
}
|
||||
if (normalizedPath === LEGACY_AGENTS_PREFIX) {
|
||||
return settingsTabPath(DEFAULT_SETTINGS_TAB);
|
||||
}
|
||||
if (normalizedPath === LEGACY_DOCKER_PREFIX) {
|
||||
return settingsTabPath(DEFAULT_SETTINGS_TAB);
|
||||
}
|
||||
// All infrastructure sub-paths collapse to the single infrastructure workspace.
|
||||
if (normalizedPath.startsWith(`${INFRASTRUCTURE_SYSTEMS_PREFIX}/`)) {
|
||||
return INFRASTRUCTURE_SYSTEMS_PREFIX;
|
||||
}
|
||||
if (normalizedPath === INFRASTRUCTURE_SYSTEMS_PREFIX) {
|
||||
return settingsTabPath(DEFAULT_SETTINGS_TAB);
|
||||
}
|
||||
@@ -167,14 +159,6 @@ export function resolveCanonicalSettingsPath(path: string): string | null {
|
||||
if (normalizedPath.startsWith(`${LEGACY_SETTINGS_OPERATIONS_PREFIX}/`)) {
|
||||
return buildLegacyOperationsSettingsPath(normalizedPath);
|
||||
}
|
||||
if (
|
||||
normalizedPath.startsWith(`${LEGACY_PROXMOX_API_PREFIX}/`) ||
|
||||
normalizedPath === LEGACY_PROXMOX_API_PREFIX ||
|
||||
normalizedPath.startsWith(`${LEGACY_PROXMOX_PREFIX}/`) ||
|
||||
normalizedPath === LEGACY_PROXMOX_PREFIX
|
||||
) {
|
||||
return INFRASTRUCTURE_SYSTEMS_PREFIX;
|
||||
}
|
||||
if (normalizedPath === LEGACY_INTEGRATIONS_API_PREFIX) {
|
||||
return SECURITY_API_PREFIX;
|
||||
}
|
||||
@@ -191,12 +175,7 @@ export function deriveTabFromPath(path: string): SettingsTab {
|
||||
const canonicalPath = resolveCanonicalSettingsPath(path) ?? normalizeSettingsPath(path);
|
||||
|
||||
if (canonicalPath === '/settings') return DEFAULT_SETTINGS_TAB;
|
||||
if (
|
||||
canonicalPath === INFRASTRUCTURE_SYSTEMS_PREFIX ||
|
||||
canonicalPath.startsWith(`${INFRASTRUCTURE_SYSTEMS_PREFIX}/`) ||
|
||||
canonicalPath === LEGACY_AGENTS_PREFIX ||
|
||||
canonicalPath === LEGACY_DOCKER_PREFIX
|
||||
) {
|
||||
if (canonicalPath === INFRASTRUCTURE_SYSTEMS_PREFIX) {
|
||||
return 'infrastructure-systems';
|
||||
}
|
||||
|
||||
@@ -206,7 +185,10 @@ export function deriveTabFromPath(path: string): SettingsTab {
|
||||
if (canonicalPath.includes('/settings/system-recovery')) return 'system-recovery';
|
||||
if (canonicalPath.includes('/settings/system-ai')) return 'system-ai';
|
||||
if (canonicalPath.includes('/settings/system-relay')) return 'system-relay';
|
||||
if (canonicalPath.includes(SYSTEM_BILLING_PREFIX) || canonicalPath.includes(LEGACY_SYSTEM_PRO_PREFIX))
|
||||
if (
|
||||
canonicalPath.includes(SYSTEM_BILLING_PREFIX) ||
|
||||
canonicalPath.includes(LEGACY_SYSTEM_PRO_PREFIX)
|
||||
)
|
||||
return 'system-billing';
|
||||
if (canonicalPath.startsWith(SUPPORT_LOGS_PREFIX)) return 'support-logs';
|
||||
if (canonicalPath.startsWith(SUPPORT_REPORTING_PREFIX)) return 'support-reporting';
|
||||
@@ -234,29 +216,6 @@ export function deriveTabFromPath(path: string): SettingsTab {
|
||||
return DEFAULT_SETTINGS_TAB;
|
||||
}
|
||||
|
||||
export function deriveAgentFromPath(path: string): AgentKey | null {
|
||||
const normalizedPath = normalizeSettingsPath(path);
|
||||
for (const [agent, meta] of Object.entries(PROXMOX_AGENT_META) as Array<
|
||||
[AgentKey, (typeof PROXMOX_AGENT_META)[AgentKey]]
|
||||
>) {
|
||||
if (normalizedPath.includes(meta.path)) return agent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isProxmoxSettingsPath(path: string): boolean {
|
||||
const normalizedPath = normalizeSettingsPath(path);
|
||||
return (
|
||||
normalizedPath.startsWith(PROXMOX_PREFIX) ||
|
||||
normalizedPath.startsWith(LEGACY_PROXMOX_PREFIX) ||
|
||||
normalizedPath.startsWith(LEGACY_PROXMOX_API_PREFIX)
|
||||
);
|
||||
}
|
||||
|
||||
export function settingsAgentPath(agent: AgentKey): string {
|
||||
return PROXMOX_AGENT_META[agent].path;
|
||||
}
|
||||
|
||||
export function settingsAgentPlatformType(agent: AgentKey): ProxmoxPlatformType {
|
||||
return PROXMOX_AGENT_META[agent].platformType;
|
||||
}
|
||||
@@ -290,13 +249,6 @@ export function deriveTabFromQuery(search: string): SettingsTab | null {
|
||||
|
||||
switch (tab) {
|
||||
case 'infrastructure':
|
||||
case 'workloads':
|
||||
case 'agents':
|
||||
return 'infrastructure-systems';
|
||||
case 'connections':
|
||||
case 'platforms':
|
||||
case 'proxmox':
|
||||
case 'install':
|
||||
return 'infrastructure-systems';
|
||||
case 'system-recovery':
|
||||
return 'system-recovery';
|
||||
@@ -324,8 +276,6 @@ export function deriveTabFromQuery(search: string): SettingsTab | null {
|
||||
return 'support-logs';
|
||||
case 'api':
|
||||
return 'api';
|
||||
case 'docker':
|
||||
return 'infrastructure-systems';
|
||||
case 'organization-overview':
|
||||
return 'organization-overview';
|
||||
case 'organization-access':
|
||||
@@ -392,6 +342,44 @@ export function settingsTabPath(tab: SettingsTab): string {
|
||||
}
|
||||
}
|
||||
|
||||
const ROUTEABLE_SETTINGS_PATHS = new Set<string>([
|
||||
settingsTabPath('infrastructure-systems'),
|
||||
settingsTabPath('system-general'),
|
||||
settingsTabPath('system-network'),
|
||||
settingsTabPath('system-updates'),
|
||||
settingsTabPath('system-recovery'),
|
||||
settingsTabPath('system-ai'),
|
||||
settingsTabPath('system-relay'),
|
||||
settingsTabPath('system-billing'),
|
||||
`${SYSTEM_BILLING_PREFIX}/usage`,
|
||||
settingsTabPath('support-diagnostics'),
|
||||
settingsTabPath('support-reporting'),
|
||||
settingsTabPath('support-logs'),
|
||||
settingsTabPath('organization-overview'),
|
||||
settingsTabPath('organization-access'),
|
||||
settingsTabPath('organization-billing'),
|
||||
settingsTabPath('organization-billing-admin'),
|
||||
settingsTabPath('organization-sharing'),
|
||||
settingsTabPath('api'),
|
||||
settingsTabPath('security-overview'),
|
||||
settingsTabPath('security-data-handling'),
|
||||
settingsTabPath('security-auth'),
|
||||
settingsTabPath('security-sso'),
|
||||
settingsTabPath('security-roles'),
|
||||
settingsTabPath('security-users'),
|
||||
settingsTabPath('security-audit'),
|
||||
settingsTabPath('security-webhooks'),
|
||||
]);
|
||||
|
||||
export function isRouteableSettingsPath(path: string): boolean {
|
||||
const normalizedPath = normalizeSettingsPath(path);
|
||||
if (normalizedPath === '/settings') return true;
|
||||
if (isRetiredSettingsCompatibilityPath(normalizedPath)) return false;
|
||||
|
||||
const canonicalPath = resolveCanonicalSettingsPath(normalizedPath);
|
||||
return canonicalPath ? ROUTEABLE_SETTINGS_PATHS.has(canonicalPath) : false;
|
||||
}
|
||||
|
||||
export function isInfrastructureSettingsTab(tab: SettingsTab): tab is InfrastructureSettingsTab {
|
||||
return tab === 'infrastructure-systems';
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
export {
|
||||
DEFAULT_SETTINGS_TAB,
|
||||
agentKeyFromPlatformType,
|
||||
deriveAgentFromPath,
|
||||
deriveTabFromPath,
|
||||
deriveTabFromQuery,
|
||||
isRetiredSettingsCompatibilityPath,
|
||||
isRouteableSettingsPath,
|
||||
resolveCanonicalSettingsPath,
|
||||
settingsAgentLabel,
|
||||
settingsAgentNodeLabel,
|
||||
settingsAgentPath,
|
||||
settingsAgentPlatformType,
|
||||
settingsTabPath,
|
||||
type AgentKey,
|
||||
|
||||
@@ -4,17 +4,11 @@ import {
|
||||
sessionPresentationPolicyResolved,
|
||||
} from '@/stores/sessionPresentationPolicy';
|
||||
import { resolveCanonicalSelfHostedBillingHref } from '@/utils/pricingHandoff';
|
||||
import {
|
||||
buildInfrastructureOnboardingPath,
|
||||
buildInfrastructureWorkspacePath,
|
||||
deriveAddStepFromLegacyPath,
|
||||
} from './infrastructureWorkspaceModel';
|
||||
import { buildInfrastructureWorkspacePath } from './infrastructureWorkspaceModel';
|
||||
import {
|
||||
DEFAULT_SETTINGS_TAB,
|
||||
deriveAgentFromPath,
|
||||
deriveTabFromPath,
|
||||
deriveTabFromQuery,
|
||||
isProxmoxSettingsPath,
|
||||
resolveCanonicalSettingsPath,
|
||||
settingsTabPath,
|
||||
type AgentKey,
|
||||
@@ -96,9 +90,7 @@ export function useSettingsNavigation({ navigate, location }: UseSettingsNavigat
|
||||
if (
|
||||
sessionPresentationPolicyResolved() &&
|
||||
presentationPolicyIsReadOnly() &&
|
||||
(path.startsWith('/settings/infrastructure') ||
|
||||
path === '/settings/workloads' ||
|
||||
path === '/settings/workloads/docker')
|
||||
path.startsWith('/settings/infrastructure')
|
||||
) {
|
||||
navigate(buildInfrastructureWorkspacePath(), {
|
||||
replace: true,
|
||||
@@ -107,24 +99,6 @@ export function useSettingsNavigation({ navigate, location }: UseSettingsNavigat
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync Proxmox agent from the raw path before any redirect so deep
|
||||
// links like /platforms/proxmox/pbs still seed the correct agent.
|
||||
if (isProxmoxSettingsPath(path)) {
|
||||
const agentFromPath = deriveAgentFromPath(path) ?? 'pve';
|
||||
if (selectedAgent() !== agentFromPath) {
|
||||
setSelectedAgent(agentFromPath);
|
||||
}
|
||||
}
|
||||
|
||||
const infrastructureOnboardingStep = deriveAddStepFromLegacyPath(path);
|
||||
if (infrastructureOnboardingStep) {
|
||||
navigate(buildInfrastructureOnboardingPath(infrastructureOnboardingStep), {
|
||||
replace: true,
|
||||
scroll: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const canonicalPath = resolveCanonicalSettingsPath(path);
|
||||
if (canonicalPath && canonicalPath !== path) {
|
||||
navigate(canonicalPath, {
|
||||
|
||||
@@ -146,7 +146,7 @@ describe('MobileNavBar', () => {
|
||||
id: 'proxmox',
|
||||
label: 'Proxmox',
|
||||
route: '/proxmox/overview',
|
||||
settingsRoute: '/settings/infrastructure/platforms/proxmox/pve',
|
||||
settingsRoute: '/settings/infrastructure',
|
||||
tooltip: 'Proxmox',
|
||||
enabled: true,
|
||||
live: true,
|
||||
|
||||
@@ -951,9 +951,7 @@ describe('frontend resource type boundaries', () => {
|
||||
expect(tagBadgesSource).toContain("from '@/components/shared/Tooltip'");
|
||||
expect(resourceDetailSummarySource).toContain("from '@/components/shared/TagBadges'");
|
||||
expect(resourceDetailDrawerOverviewSource).toContain('getAllFilterOptionLabel');
|
||||
expect(resourceDetailSummarySource).not.toContain(
|
||||
"from '@/components/Workloads/TagBadges'",
|
||||
);
|
||||
expect(resourceDetailSummarySource).not.toContain("from '@/components/Workloads/TagBadges'");
|
||||
expect(resourceDetailDrawerShellSource).toContain("from './ResourceDetailDrawerOverviewTab'");
|
||||
expect(resourceDetailDrawerShellSource).toContain("from './ResourceDetailDrawerDebugTab'");
|
||||
expect(resourceDetailDrawerStateSource).toContain(
|
||||
@@ -3317,7 +3315,7 @@ describe('frontend resource type boundaries', () => {
|
||||
'@/components/Settings/infrastructureWorkspaceModel'.replace('@/components/Settings/', './'),
|
||||
);
|
||||
expect(infrastructureWorkspaceSource).not.toContain('inferViewFromPath');
|
||||
expect(infrastructureWorkspaceModelSource).toContain(
|
||||
expect(infrastructureWorkspaceModelSource).not.toContain(
|
||||
'export function deriveAddStepFromLegacyPath',
|
||||
);
|
||||
expect(infrastructureWorkspaceModelSource).toContain(
|
||||
|
||||
Reference in New Issue
Block a user