diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 8cfa727df..a566bdc37 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -356,16 +356,25 @@ an add-only capacity posture. before non-default connection controls. 7. Keep `frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx` and `frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts` - aligned with that same lifecycle path. Bare infrastructure settings routes - must default to the install workspace, and the workspace shell must make - the first-host sequence explicit before operators drift into reporting and - control surfaces. The third workspace subtab owns the reporting-and-control - surface at route `/settings/infrastructure/operations`; its user-facing - label is `Inventory` so the workspace narrative leads new operators from - install-or-connect into a reporting surface named after what it shows, not - after the internal lifecycle stage. The first-host orientation card must - hide once any platform connection or agent resource is already reporting, - so established operators do not see a first-system prompt. + aligned with that same lifecycle path. The bare + `/settings/infrastructure` route must render a unified Connections table + that lists every monitored system — Proxmox VE, PBS, PMG, TrueNAS, VMware, + and agent hosts — as sibling rows sharing a single name/kind/method/status/ + last-reported shape, so operators read what is currently being monitored + in one scan instead of tab-hopping between per-kind surfaces. Adding a new + system must be a single entry point on that table: an `Add a system` + picker whose tiles route the operator into the right flow per kind + (`/settings/infrastructure/install` for the agent choice, + `/settings/infrastructure/platforms/` for Proxmox/TrueNAS/VMware + tiles). `/settings/infrastructure/install`, + `/settings/infrastructure/platforms`, and + `/settings/infrastructure/operations` remain reachable as detail routes + for install, platform connections, and legacy reporting/control surfaces + respectively, but the workspace shell must not gate inventory visibility + behind tab navigation. Read-only sessions must continue to redirect the + install detail route back to the unified inventory view and suppress the + add-system entry point on the base table so presentation-policy + restrictions still hold. 8. Keep post-install lifecycle completion explicit inside `frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx` and `frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx`. diff --git a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx index 4bb6f9f68..0c1639d60 100644 --- a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx +++ b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx @@ -1,17 +1,15 @@ -import { Component, Match, Show, Switch, createEffect, createMemo } from 'solid-js'; +import { Component, Match, Switch, createEffect, createMemo, createSignal } from 'solid-js'; import { useLocation, useNavigate } from '@solidjs/router'; -import { Card } from '@/components/shared/Card'; -import { Subtabs } from '@/components/shared/Subtabs'; import { presentationPolicyIsReadOnly } from '@/stores/sessionPresentationPolicy'; -import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from './selfHostedBillingPresentation'; import { InfrastructureInstallPanel } from './InfrastructureInstallPanel'; import { InfrastructureReportingPanel } from './InfrastructureReportingPanel'; import { PlatformConnectionsWorkspace } from './PlatformConnectionsWorkspace'; +import { ConnectionsTable } from './ConnectionsTable'; +import { AddSystemPicker, type AddSystemChoice } from './AddSystemPicker'; +import { buildConnectionRows, type ConnectionRow } from './connectionsTableModel'; import { - INFRASTRUCTURE_WORKSPACE_TABS, buildInfrastructureWorkspacePath, getInfrastructureWorkspaceViewFromPath, - type InfrastructureWorkspaceView, } from './infrastructureWorkspaceModel'; import type { InfrastructurePlatformSettingsProps } from './proxmoxSettingsModel'; @@ -22,126 +20,58 @@ export const InfrastructureWorkspace: Component = const location = useLocation(); const activeView = createMemo(() => getInfrastructureWorkspaceViewFromPath(location.pathname)); const readOnlyWorkspace = createMemo(() => presentationPolicyIsReadOnly()); - const installPath = createMemo(() => buildInfrastructureWorkspacePath('install')); - const platformsPath = createMemo(() => buildInfrastructureWorkspacePath('platforms')); - const inventoryPath = createMemo(() => buildInfrastructureWorkspacePath('inventory')); - const visibleTabs = createMemo(() => - readOnlyWorkspace() - ? INFRASTRUCTURE_WORKSPACE_TABS.filter((tab) => tab.id === 'inventory') - : INFRASTRUCTURE_WORKSPACE_TABS, - ); - const hasAnySystem = createMemo(() => { - const summary = props.platformConnectionsSummary?.(); - const platformCount = summary - ? summary.pveCount + - summary.pbsCount + - summary.pmgCount + - summary.truenasCount + - summary.vmwareCount - : 0; - const agentCount = props.agentStateResources?.()?.length ?? 0; - return platformCount + agentCount > 0; - }); - const showOrientation = createMemo(() => !readOnlyWorkspace() && !hasAnySystem()); + const [pickerOpen, setPickerOpen] = createSignal(false); - const openView = (view: InfrastructureWorkspaceView) => - navigate(buildInfrastructureWorkspacePath(view)); + const rows = createMemo(() => + buildConnectionRows({ + pveNodes: props.pveNodes(), + pbsNodes: props.pbsNodes(), + pmgNodes: props.pmgNodes(), + truenasConnections: props.trueNASSettings.connections(), + vmwareConnections: props.vmwareSettings.connections(), + agentResources: props.agentStateResources?.() ?? [], + }), + ); + + const handleAddSystem = (choice: AddSystemChoice) => { + setPickerOpen(false); + if (choice.kind === 'agent') { + navigate('/settings/infrastructure/install'); + return; + } + if (choice.kind === 'truenas') { + navigate('/settings/infrastructure/platforms/truenas'); + return; + } + if (choice.kind === 'vmware') { + navigate('/settings/infrastructure/platforms/vmware'); + return; + } + props.onSelectAgent(choice.kind); + navigate('/settings/infrastructure/platforms/proxmox'); + }; createEffect(() => { - if (readOnlyWorkspace() && activeView() !== 'inventory') { - navigate(inventoryPath(), { replace: true }); + if (readOnlyWorkspace() && activeView() === 'install') { + navigate(buildInfrastructureWorkspacePath('inventory'), { replace: true }); } }); return (
- - -
-
-

Connect your first system

-

- Use Install on a host for the first machine that should run the unified agent. If - the first system is API-backed, such as Proxmox or TrueNAS, go straight to Platform - connections. -

-
-
-
-

- 1. Choose path -

-

- Choose Install on a host for agent-managed systems, or open Platform connections - for Proxmox, TrueNAS, and other systems Pulse should poll through their own APIs. -

-
-
-

- 2. Generate access -

-

- Create the install token Pulse expects for the first monitored host, or add the - API credentials Pulse should store for API-backed platforms like Proxmox and - TrueNAS. -

-
-
-

- 3. Confirm reporting -

-

- Run the command on that machine, then open Inventory once the first system starts - reporting. -

-
-
-
- - -
-

- {SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral} -

-
-
-
- -
- openView(value as InfrastructureWorkspaceView)} - ariaLabel="Infrastructure workspace" - tabs={visibleTabs().map((tab) => ({ - value: tab.id, - label: tab.label, - }))} - /> -
- + + setPickerOpen(true)} + /> + setPickerOpen(false)} + onSelect={handleAddSystem} + /> + + @@ -150,10 +80,12 @@ export const InfrastructureWorkspace: Component = - + openView('platforms')} + onManagePlatformConnections={() => + navigate('/settings/infrastructure/platforms') + } /> diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsController.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsController.test.tsx index d69d7b02f..188a141ab 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsController.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsController.test.tsx @@ -1054,7 +1054,7 @@ describe('InfrastructureOperationsController agent lookup', () => { expect(navigateMock).toHaveBeenCalledWith('/dashboard'); fireEvent.click(screen.getByRole('button', { name: 'Open inventory' })); - expect(navigateMock).toHaveBeenCalledWith('/settings/infrastructure/operations'); + expect(navigateMock).toHaveBeenCalledWith('/settings/infrastructure'); }); it('shows error message when agent is not found', async () => { diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx index bd611ede2..c7b983075 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx @@ -1,9 +1,8 @@ import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from '../selfHostedBillingPresentation'; import { InfrastructureWorkspace } from '../InfrastructureWorkspace'; -let mockPathname = '/settings'; +let mockPathname = '/settings/infrastructure'; const navigateSpy = vi.hoisted(() => vi.fn()); const presentationPolicyIsReadOnlyMock = vi.hoisted(() => vi.fn(() => false)); @@ -21,22 +20,46 @@ vi.mock('@/stores/sessionPresentationPolicy', () => ({ })); vi.mock('../InfrastructureInstallPanel', () => ({ - InfrastructureInstallPanel: () =>
install
, + InfrastructureInstallPanel: () =>
install
, })); vi.mock('../InfrastructureReportingPanel', () => ({ - InfrastructureReportingPanel: () =>
profiles
, + InfrastructureReportingPanel: () =>
operations
, })); vi.mock('../PlatformConnectionsWorkspace', () => ({ PlatformConnectionsWorkspace: () =>
platforms
, })); +const onSelectAgentSpy = vi.fn(); + +const baseProps = () => + ({ + pveNodes: () => [], + pbsNodes: () => [], + pmgNodes: () => [], + agentStateResources: () => [], + trueNASSettings: { connections: () => [] }, + vmwareSettings: { connections: () => [] }, + platformConnectionsSummary: () => ({ + pveCount: 0, + pbsCount: 0, + pmgCount: 0, + truenasCount: 0, + truenasAvailable: true, + vmwareCount: 0, + vmwareAvailable: true, + }), + selectedAgent: () => 'pve', + onSelectAgent: onSelectAgentSpy, + }) as any; + describe('InfrastructureWorkspace', () => { beforeEach(() => { navigateSpy.mockReset(); presentationPolicyIsReadOnlyMock.mockReset(); presentationPolicyIsReadOnlyMock.mockReturnValue(false); + onSelectAgentSpy.mockReset(); mockPathname = '/settings/infrastructure'; }); @@ -44,170 +67,111 @@ describe('InfrastructureWorkspace', () => { cleanup(); }); - const renderWorkspace = () => - render( - () => - ( - [], - pbsNodes: () => [], - pmgNodes: () => [], - } as any)} - /> - ) as any, - ); + const renderWorkspace = (propOverrides: Record = {}) => + render(() => () as any); - it('defaults bare infrastructure routing to install on a host', () => { + it('renders the unified connections table at the base infrastructure route', () => { renderWorkspace(); - const tablist = screen.getByRole('tablist', { name: 'Infrastructure workspace' }); - expect(tablist).toBeInTheDocument(); - expect(screen.getByText('Connect your first system')).toBeInTheDocument(); - expect( - screen.getByText( - 'Use Install on a host for the first machine that should run the unified agent. If the first system is API-backed, such as Proxmox or TrueNAS, go straight to Platform connections.', - ), - ).toBeInTheDocument(); - expect(screen.getByText('1. Choose path')).toBeInTheDocument(); - expect(screen.getByText('2. Generate access')).toBeInTheDocument(); - expect(screen.getByText('3. Confirm reporting')).toBeInTheDocument(); - expect( - screen.getByText(SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral), - ).toBeInTheDocument(); - expect(screen.getByRole('tab', { name: 'Install on a host' })).toHaveAttribute( - 'aria-selected', - 'true', - ); - expect(screen.getByRole('tab', { name: 'Platform connections' })).toHaveAttribute( - 'aria-selected', - 'false', - ); - expect(screen.getByRole('tab', { name: 'Inventory' })).toHaveAttribute( - 'aria-selected', - 'false', - ); - expect(screen.getByTestId('unified-agents')).toBeInTheDocument(); + expect(screen.getByText('Connections')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Add a system/i })).toBeInTheDocument(); + expect(screen.queryByTestId('install-panel')).toBeNull(); + expect(screen.queryByTestId('platform-connections')).toBeNull(); }); - it('uses the shared subtabs to switch to platform connections', () => { - renderWorkspace(); + it('merges every connection source into a single alpha-sorted table', () => { + renderWorkspace({ + pveNodes: () => [ + { id: 'n1', name: 'zeus', host: '10.0.0.1', type: 'pve', status: 'connected' }, + ], + agentStateResources: () => [ + { id: 'a1', name: 'tower', displayName: 'tower', status: 'online', lastSeen: Date.now() }, + ], + trueNASSettings: { + connections: () => [ + { id: 't1', name: 'nas.home', host: '10.0.0.2', enabled: true, insecureSkipVerify: false, useHttps: true }, + ], + }, + }); - fireEvent.click(screen.getByRole('tab', { name: 'Platform connections' })); - - expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms'); + const rowNames = screen.getAllByText(/zeus|tower|nas\.home/).map((el) => el.textContent); + expect(rowNames).toContain('nas.home'); + expect(rowNames).toContain('tower'); + expect(rowNames).toContain('zeus'); }); - it('renders the platform workspace from the router pathname', () => { - mockPathname = '/settings/infrastructure/platforms'; + it('opens the add-system picker when the add button is clicked', () => { renderWorkspace(); - expect(screen.getByRole('tab', { name: 'Platform connections' })).toHaveAttribute( - 'aria-selected', - 'true', - ); - expect(screen.getByTestId('platform-connections')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: /Add a system/i })); + + expect(screen.getByText('Linux or Docker host (agent)')).toBeInTheDocument(); + expect(screen.getByText('Proxmox VE')).toBeInTheDocument(); + expect(screen.getByText('TrueNAS SCALE')).toBeInTheDocument(); }); - it('keeps the reporting route available for established operators', () => { - mockPathname = '/settings/infrastructure/operations'; + it('routes the agent-host choice to the dedicated install workspace', () => { renderWorkspace(); + fireEvent.click(screen.getByRole('button', { name: /Add a system/i })); - expect(screen.getByRole('tab', { name: 'Inventory' })).toHaveAttribute( - 'aria-selected', - 'true', - ); - expect(screen.getByTestId('agent-profiles')).toBeInTheDocument(); - }); - - it('uses the guided workspace actions to open install and platform paths', () => { - renderWorkspace(); - - fireEvent.click(screen.getByRole('button', { name: 'Open Install on a host' })); - fireEvent.click(screen.getByRole('button', { name: 'Open Platform connections' })); - - expect(navigateSpy).toHaveBeenNthCalledWith(1, '/settings/infrastructure/install'); - expect(navigateSpy).toHaveBeenNthCalledWith(2, '/settings/infrastructure/platforms'); - }); - - it('returns to the base settings route when switching away from platform connections', () => { - mockPathname = '/settings/infrastructure/platforms'; - renderWorkspace(); - - fireEvent.click(screen.getByRole('tab', { name: 'Install on a host' })); + fireEvent.click(screen.getByText('Linux or Docker host (agent)')); expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/install'); }); - it('hides the first-system orientation card once any platform connection exists', () => { - render( - () => - ( - [], - pbsNodes: () => [], - pmgNodes: () => [], - agentStateResources: () => [], - platformConnectionsSummary: () => ({ - pveCount: 1, - pbsCount: 0, - pmgCount: 0, - truenasCount: 0, - truenasAvailable: true, - vmwareCount: 0, - vmwareAvailable: true, - }), - } as any)} - /> - ) as any, - ); + it('routes TrueNAS and VMware choices to their platform panels', () => { + renderWorkspace(); + fireEvent.click(screen.getByRole('button', { name: /Add a system/i })); + fireEvent.click(screen.getByText('TrueNAS SCALE')); + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/truenas'); - expect(screen.queryByText('Connect your first system')).not.toBeInTheDocument(); - expect(screen.getByRole('tab', { name: 'Install on a host' })).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: /Add a system/i })); + fireEvent.click(screen.getByText('VMware vSphere or ESXi')); + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/vmware'); }); - it('hides the first-system orientation card once any agent resource is reporting', () => { - render( - () => - ( - [], - pbsNodes: () => [], - pmgNodes: () => [], - agentStateResources: () => [{ id: 'agent-1' }], - platformConnectionsSummary: () => ({ - pveCount: 0, - pbsCount: 0, - pmgCount: 0, - truenasCount: 0, - truenasAvailable: true, - vmwareCount: 0, - vmwareAvailable: true, - }), - } as any)} - /> - ) as any, - ); + it('preselects the Proxmox kind and lands on the platforms route for PVE, PBS, and PMG', () => { + renderWorkspace(); - expect(screen.queryByText('Connect your first system')).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: /Add a system/i })); + fireEvent.click(screen.getByText('Proxmox Backup Server')); + + expect(onSelectAgentSpy).toHaveBeenCalledWith('pbs'); + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/proxmox'); }); - it('collapses the workspace to reporting and redirects install routes in read-only mode', () => { + it('renders the install workspace when the URL is /install', () => { + mockPathname = '/settings/infrastructure/install'; + renderWorkspace(); + expect(screen.getByTestId('install-panel')).toBeInTheDocument(); + }); + + it('renders the platforms workspace when the URL is under /platforms', () => { + mockPathname = '/settings/infrastructure/platforms/truenas'; + renderWorkspace(); + expect(screen.getByTestId('platform-connections')).toBeInTheDocument(); + }); + + it('keeps /operations reachable as a legacy detail route', () => { + mockPathname = '/settings/infrastructure/operations'; + renderWorkspace(); + expect(screen.getByTestId('reporting-panel')).toBeInTheDocument(); + }); + + it('hides the add-system action and redirects install routes in read-only mode', () => { presentationPolicyIsReadOnlyMock.mockReturnValue(true); mockPathname = '/settings/infrastructure/install'; renderWorkspace(); - expect(screen.queryByText('Connect your first system')).not.toBeInTheDocument(); - expect(screen.queryByRole('tab', { name: 'Install on a host' })).not.toBeInTheDocument(); - expect(screen.queryByRole('tab', { name: 'Platform connections' })).not.toBeInTheDocument(); - expect(screen.getByRole('tab', { name: 'Inventory' })).toHaveAttribute( - 'aria-selected', - 'false', - ); - expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/operations', { - replace: true, - }); + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure', { replace: true }); + }); + + it('still renders the connections table without an add button in read-only mode', () => { + presentationPolicyIsReadOnlyMock.mockReturnValue(true); + mockPathname = '/settings/infrastructure'; + renderWorkspace(); + + expect(screen.getByText('Connections')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /Add a system/i })).toBeNull(); }); }); diff --git a/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts b/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts index 9cead8463..a41993af4 100644 --- a/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/monitoredSystemModelGuardrails.test.ts @@ -785,7 +785,7 @@ describe('monitored-system model guardrails', () => { "pathname.startsWith('/settings/infrastructure/proxmox')", ); expect(infrastructureWorkspaceModelSource).toContain( - "path: '/settings/infrastructure/operations'", + "operations: '/settings/infrastructure/operations'", ); }); diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index 678d81dbf..c633cc28e 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -593,12 +593,9 @@ describe('Settings architecture guardrails', () => { expect(proLicensePanelSource).not.toContain('description="Manage self-hosted billing'); expect(proLicensePanelSource).not.toContain('title="Plan"'); expect(proLicensePanelSource).not.toContain('title="Usage"'); - expect(infrastructureWorkspaceSource).toContain('./selfHostedBillingPresentation'); - expect(infrastructureWorkspaceSource).toContain( - 'SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral', - ); + expect(infrastructureWorkspaceSource).not.toContain('./selfHostedBillingPresentation'); expect(infrastructureWorkspaceSource).not.toContain( - 'Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro, not here.', + 'SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral', ); expect(proLicensePlanSectionSource).toContain('CommercialStatGrid'); expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState'); @@ -892,7 +889,7 @@ describe('Settings architecture guardrails', () => { ); expect(infrastructureWorkspaceSource).toContain('createEffect(() =>'); expect(infrastructureWorkspaceSource).toContain( - "if (readOnlyWorkspace() && activeView() !== 'inventory')", + "if (readOnlyWorkspace() && activeView() === 'install')", ); expect(infrastructureWorkspaceSource).toContain('InfrastructureInstallPanel'); expect(infrastructureWorkspaceSource).toContain('PlatformConnectionsWorkspace'); @@ -958,8 +955,8 @@ describe('Settings architecture guardrails', () => { ); expect(infrastructureActiveRowDetailsSource).toContain('useInfrastructureOperationsContext'); expect(infrastructureIgnoredRowDetailsSource).toContain('useInfrastructureOperationsContext'); - expect(infrastructureWorkspaceModelSource).toContain( - 'export const INFRASTRUCTURE_WORKSPACE_TABS', + expect(infrastructureWorkspaceModelSource).not.toContain( + 'INFRASTRUCTURE_WORKSPACE_TABS', ); expect(infrastructureWorkspaceModelSource).toContain( 'export function getInfrastructureWorkspaceViewFromPath', @@ -1501,7 +1498,7 @@ describe('Settings architecture guardrails', () => { 'Setup changes stay unavailable in this read-only session.', ); expect(infrastructureWorkspaceSource).toContain('presentationPolicyIsReadOnly'); - expect(infrastructureWorkspaceSource).toContain("tab.id === 'inventory'"); + expect(infrastructureWorkspaceSource).toContain("activeView() === 'inventory'"); }); it('keeps relay shell copy on the shared relay presentation owner', () => { diff --git a/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx b/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx index e1aafbace..dd10ed610 100644 --- a/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/useSettingsNavigation.test.tsx @@ -44,7 +44,7 @@ describe('useSettingsNavigation', () => { renderHarness('/settings'); await waitFor(() => { - expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/operations', { + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure', { replace: true, scroll: false, }); @@ -57,7 +57,7 @@ describe('useSettingsNavigation', () => { fireEvent.click(screen.getByRole('button', { name: 'open infrastructure settings' })); - expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/operations', { + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure', { scroll: false, }); }); diff --git a/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts b/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts index a2eaa7db9..0c8c58edd 100644 --- a/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts +++ b/frontend-modern/src/components/Settings/infrastructureWorkspaceModel.ts @@ -1,28 +1,11 @@ -export type InfrastructureWorkspaceView = 'install' | 'platforms' | 'inventory'; +export type InfrastructureWorkspaceView = 'inventory' | 'install' | 'platforms' | 'operations'; -export interface InfrastructureWorkspaceTabDefinition { - id: InfrastructureWorkspaceView; - label: string; - path: string; -} - -export const INFRASTRUCTURE_WORKSPACE_TABS: readonly InfrastructureWorkspaceTabDefinition[] = [ - { - id: 'install', - label: 'Install on a host', - path: '/settings/infrastructure/install', - }, - { - id: 'platforms', - label: 'Platform connections', - path: '/settings/infrastructure/platforms', - }, - { - id: 'inventory', - label: 'Inventory', - path: '/settings/infrastructure/operations', - }, -]; +const INFRASTRUCTURE_WORKSPACE_PATHS: Record = { + inventory: '/settings/infrastructure', + install: '/settings/infrastructure/install', + platforms: '/settings/infrastructure/platforms', + operations: '/settings/infrastructure/operations', +}; export function getInfrastructureWorkspaceViewFromPath( pathname: string, @@ -35,20 +18,15 @@ export function getInfrastructureWorkspaceViewFromPath( ) { return 'platforms'; } - if (pathname.startsWith('/settings/infrastructure/operations')) { - return 'inventory'; - } if (pathname.startsWith('/settings/infrastructure/install')) { return 'install'; } - return 'install'; + if (pathname.startsWith('/settings/infrastructure/operations')) { + return 'operations'; + } + return 'inventory'; } -export function buildInfrastructureWorkspacePath( - view: InfrastructureWorkspaceView, -): string { - return ( - INFRASTRUCTURE_WORKSPACE_TABS.find((tab) => tab.id === view)?.path ?? - '/settings/infrastructure/install' - ); +export function buildInfrastructureWorkspacePath(view: InfrastructureWorkspaceView): string { + return INFRASTRUCTURE_WORKSPACE_PATHS[view]; }