diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 5f5ae8a08..a2ac6a57c 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -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=`. 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 diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 64a67c9e1..a332b5359 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -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; diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index dc914b569..d164058b5 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -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 diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 7acdbdddc..47c83a4f2 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -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 diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 7f763a9c3..8ab7e1941 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -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=` 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 diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 529505bb2..b4f1b7f5c 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -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", diff --git a/frontend-modern/src/AppLayout.tsx b/frontend-modern/src/AppLayout.tsx index 17db30487..0bf2c304a 100644 --- a/frontend-modern/src/AppLayout.tsx +++ b/frontend-modern/src/AppLayout.tsx @@ -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'), diff --git a/frontend-modern/src/__tests__/App.architecture.test.ts b/frontend-modern/src/__tests__/App.architecture.test.ts index ec84c2a5a..835dd212e 100644 --- a/frontend-modern/src/__tests__/App.architecture.test.ts +++ b/frontend-modern/src/__tests__/App.architecture.test.ts @@ -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(' { 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(' props.connectionStatus();'); expect(appLayoutSource).toContain( diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index 21258925d..609d82f77 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -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 = () => ( ); -const Settings: Component = (props) => { +const SettingsWorkspace: Component = (props) => { const { state, connected: _connected } = useWebSocket(); const navigate = useNavigate(); const location = useLocation(); @@ -219,4 +221,15 @@ const Settings: Component = (props) => { ); }; +const Settings: Component = (props) => { + const location = useLocation(); + const isRouteableSettingsRoute = createMemo(() => isRouteableSettingsPath(location.pathname)); + + return ( + }> + + + ); +}; + export default Settings; diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx index 61ccc73f8..371cceb30 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx @@ -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(); }); diff --git a/frontend-modern/src/components/Settings/__tests__/infrastructureWorkspaceModel.test.ts b/frontend-modern/src/components/Settings/__tests__/infrastructureWorkspaceModel.test.ts index 7832a1975..b58aea3b8 100644 --- a/frontend-modern/src/components/Settings/__tests__/infrastructureWorkspaceModel.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/infrastructureWorkspaceModel.test.ts @@ -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(); }); }); diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index d9a7df29b..228013dc5 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -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(', ); diff --git a/frontend-modern/src/components/Settings/__tests__/settingsRouting.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsRouting.test.ts index 30f0d76f1..171ab6e19 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsRouting.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsRouting.test.ts @@ -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'); diff --git a/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx b/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx index e5c94a432..1cf2ee598 100644 --- a/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx @@ -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(); }); }); }); diff --git a/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx b/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx index b54dbed20..637c79c00 100644 --- a/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx +++ b/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx @@ -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 = ''; +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'; diff --git a/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts b/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts index 987f4acc3..42f343fa9 100644 --- a/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts +++ b/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts @@ -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); } diff --git a/frontend-modern/src/components/Settings/platformConnectionsModel.ts b/frontend-modern/src/components/Settings/platformConnectionsModel.ts deleted file mode 100644 index 8bbd3ab51..000000000 --- a/frontend-modern/src/components/Settings/platformConnectionsModel.ts +++ /dev/null @@ -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 - ); -} diff --git a/frontend-modern/src/components/Settings/settingsNavigationModel.ts b/frontend-modern/src/components/Settings/settingsNavigationModel.ts index 6f827b384..1d1a26f11 100644 --- a/frontend-modern/src/components/Settings/settingsNavigationModel.ts +++ b/frontend-modern/src/components/Settings/settingsNavigationModel.ts @@ -86,15 +86,11 @@ export type SettingsHeaderMetaMap = Record; // 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([ + 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'; } diff --git a/frontend-modern/src/components/Settings/settingsRouting.ts b/frontend-modern/src/components/Settings/settingsRouting.ts index f288ca600..eff2edac5 100644 --- a/frontend-modern/src/components/Settings/settingsRouting.ts +++ b/frontend-modern/src/components/Settings/settingsRouting.ts @@ -1,13 +1,13 @@ export { DEFAULT_SETTINGS_TAB, agentKeyFromPlatformType, - deriveAgentFromPath, deriveTabFromPath, deriveTabFromQuery, + isRetiredSettingsCompatibilityPath, + isRouteableSettingsPath, resolveCanonicalSettingsPath, settingsAgentLabel, settingsAgentNodeLabel, - settingsAgentPath, settingsAgentPlatformType, settingsTabPath, type AgentKey, diff --git a/frontend-modern/src/components/Settings/useSettingsNavigation.ts b/frontend-modern/src/components/Settings/useSettingsNavigation.ts index 148d0ff42..2b89f9446 100644 --- a/frontend-modern/src/components/Settings/useSettingsNavigation.ts +++ b/frontend-modern/src/components/Settings/useSettingsNavigation.ts @@ -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, { diff --git a/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx b/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx index 8233fc84d..9ffd5db78 100644 --- a/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx +++ b/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx @@ -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, diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index 0f237ac70..881f8a392 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -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(