diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index cd8833e4f..c4a26249a 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -858,19 +858,20 @@ ledger. Inline detail drawers may surface reporting-item and ignored-item controls, but installer setup, platform-specific configuration, and profile management must remain secondary flows rather than being dumped underneath the default ledger. -Those secondary infrastructure views must open through route-backed drawers -rather than replacing the ledger body or stacking beneath it. When the +Those secondary infrastructure views must open through route-backed modal work +surfaces rather than replacing the ledger body or stacking beneath it. When the operator opens platform connection management, install tooling, or the legacy operations workspace route, `InfrastructureWorkspace.tsx` must keep the same top-level systems ledger anchored in place and layer the secondary surface in a -drawer that can close back to `/settings/infrastructure` without trapping the -user. +centered modal that can close back to `/settings/infrastructure` without +trapping the user or relegating primary setup work to an off-canvas detail +pattern. That infrastructure surface must now stay single-purpose per route-backed -drawer: the default systems view owns the top-level monitored-system ledger, -the connections drawer owns API-backed platform management, and the install -drawer owns Linux/Windows/macOS/FreeBSD command generation. The shared +modal: the default systems view owns the top-level monitored-system ledger, +the connections modal owns API-backed platform management, and the install +modal owns Linux/Windows/macOS/FreeBSD command generation. The shared Settings sidebar owns only the top-level `Infrastructure` destination; movement -between those three jobs belongs to explicit drawer actions inside +between those three jobs belongs to explicit ledger actions inside `InfrastructureWorkspace.tsx`, not extra sidebar entries or body-replacing workspace subtabs. That same lifecycle-owned platform-connections workspace must keep API-backed diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 7a1461775..1152be0ec 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -220,15 +220,17 @@ work extends shared components instead of creating new local variants. connections must stay in their own management workspace instead of showing up as peer rows in the default table, while installer tooling, provider setup workspaces, and profile management remain secondary flows opened by explicit - deep links or drawers instead of being dumped inline underneath the default + deep links or modal work surfaces instead of being dumped inline underneath the default table. Those deep-linked secondary views must keep the systems ledger mounted and - open inside route-backed drawers instead of replacing the page body or + open inside route-backed modal surfaces instead of replacing the page body or stacking another inline workspace underneath it, so the operator always - stays anchored to the same canonical ledger while managing setup flows. + stays anchored to the same canonical ledger while managing setup flows + without relegating primary setup work to off-canvas side drawers. That same shared shell boundary now owns one canonical infrastructure destination in the Settings sidebar. `InfrastructureWorkspace.tsx` owns the - one default ledger plus route-backed `Connections` and `Install` drawers + one default ledger plus route-backed `Connections` and `Install` modal work + surfaces inside that destination, while each secondary flow still stays single-purpose instead of stacking multiple workspace surfaces at once. 6. Keep Proxmox deep-link route selection on the shared settings-navigation boundary. `frontend-modern/src/components/Settings/settingsNavigationModel.ts` and `frontend-modern/src/components/Settings/useSettingsNavigation.ts` must treat the canonical PBS and PMG Proxmox deep links as agent-selection authority even though those URLs resolve to the shared `infrastructure-operations` tab. Reloading or remounting on a PBS or PMG deep link must not silently fall back to the PVE selector state. diff --git a/frontend-modern/src/components/Settings/AddSystemPicker.tsx b/frontend-modern/src/components/Settings/AddSystemPicker.tsx index faab794a4..4d8ad7624 100644 --- a/frontend-modern/src/components/Settings/AddSystemPicker.tsx +++ b/frontend-modern/src/components/Settings/AddSystemPicker.tsx @@ -62,6 +62,7 @@ interface AddSystemPickerProps { isOpen: boolean; onClose: () => void; onSelect: (choice: AddSystemChoice) => void; + onManageProfiles?: () => void; } export const AddSystemPicker: Component = (props) => { @@ -77,10 +78,10 @@ export const AddSystemPicker: Component = (props) => {

- Add a system + Add infrastructure

- Pick what you are connecting. Pulse will route you straight into the right flow. + Choose the system or platform you want Pulse to start monitoring.

+
+ )} + ); diff --git a/frontend-modern/src/components/Settings/ConnectionsTable.tsx b/frontend-modern/src/components/Settings/ConnectionsTable.tsx index aa54f1eac..e23dd6138 100644 --- a/frontend-modern/src/components/Settings/ConnectionsTable.tsx +++ b/frontend-modern/src/components/Settings/ConnectionsTable.tsx @@ -93,7 +93,9 @@ export const ConnectionsTable: Component = (props) => {
{row.host}
-
{row.subtitle}
+ +
{row.subtitle}
+
diff --git a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx index e0f2c6cd7..3cb66179b 100644 --- a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx +++ b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx @@ -50,16 +50,6 @@ const InfrastructureWorkspaceContent: Component = readOnlyWorkspace() ? [] : [ - { - label: 'Connections', - onSelect: () => navigate(buildInfrastructureWorkspacePath('platforms'), { scroll: false }), - tone: 'secondary' as const, - }, - { - label: 'Agent profiles', - onSelect: () => setProfilesOpen(true), - tone: 'secondary' as const, - }, { label: 'Add infrastructure', onSelect: () => setPickerOpen(true), @@ -164,6 +154,10 @@ const InfrastructureWorkspaceContent: Component = isOpen={pickerOpen()} onClose={() => setPickerOpen(false)} onSelect={handleAddSystem} + onManageProfiles={() => { + setPickerOpen(false); + setProfilesOpen(true); + }} /> @@ -171,20 +165,45 @@ const InfrastructureWorkspaceContent: Component = setProfilesOpen(false)} - layout="drawer-right" - panelClass="max-w-[960px]" + panelClass="h-[calc(100dvh-2rem)] w-full max-w-[1100px]" ariaLabel="Agent profiles" > -
- +
+
+
+
+
Agent profiles
+
+ Manage reusable install defaults for agent-based systems. +
+
+ +
+
+
+ +
@@ -225,8 +244,7 @@ const InfrastructureWorkspaceContent: Component =
diff --git a/frontend-modern/src/components/Settings/__tests__/AddSystemPicker.test.tsx b/frontend-modern/src/components/Settings/__tests__/AddSystemPicker.test.tsx index 981aefd45..3f315c9be 100644 --- a/frontend-modern/src/components/Settings/__tests__/AddSystemPicker.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/AddSystemPicker.test.tsx @@ -21,12 +21,12 @@ describe('AddSystemPicker', () => { expect(screen.getByText(choice.title)).toBeInTheDocument(); } expect(ADD_SYSTEM_CHOICES.map((c) => c.kind)).toEqual([ + 'agent', 'pve', 'pbs', 'pmg', 'truenas', 'vmware', - 'agent', ]); }); @@ -52,6 +52,21 @@ describe('AddSystemPicker', () => { expect(onClose).toHaveBeenCalledTimes(1); }); + it('offers agent profile management as a secondary action when provided', () => { + const onManageProfiles = vi.fn(); + render(() => ( + {}} + onSelect={() => {}} + onManageProfiles={onManageProfiles} + /> + ) as any); + + fireEvent.click(screen.getByRole('button', { name: 'Manage agent profiles' })); + expect(onManageProfiles).toHaveBeenCalledTimes(1); + }); + it('marks the agent-host choice as an agent install and everything else as an API connection', () => { const agent = ADD_SYSTEM_CHOICES.find((c) => c.kind === 'agent'); const apis = ADD_SYSTEM_CHOICES.filter((c) => c.kind !== 'agent'); diff --git a/frontend-modern/src/components/Settings/__tests__/ConnectionsTable.test.tsx b/frontend-modern/src/components/Settings/__tests__/ConnectionsTable.test.tsx index ee46d0184..5c2c1714d 100644 --- a/frontend-modern/src/components/Settings/__tests__/ConnectionsTable.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ConnectionsTable.test.tsx @@ -6,7 +6,7 @@ import type { InfrastructureSystemRow } from '../connectionsTableModel'; const row = (overrides: Partial = {}): InfrastructureSystemRow => ({ id: 'row-1', name: 'tower', - subtitle: 'Monitored system', + subtitle: undefined, host: '10.0.0.1', coverageLabels: ['Host telemetry'], collectionLabel: 'Agent', @@ -53,7 +53,6 @@ describe('ConnectionsTable', () => { expect(screen.getByRole('table')).toBeInTheDocument(); expect(screen.getByText('tower')).toBeInTheDocument(); expect(screen.getByText('pbs-docker')).toBeInTheDocument(); - expect(screen.getByText('Monitored system')).toBeInTheDocument(); expect(screen.getByText('Ignored by Pulse')).toBeInTheDocument(); expect(screen.getByText('Host telemetry')).toBeInTheDocument(); expect(screen.getByText('API')).toBeInTheDocument(); @@ -73,16 +72,10 @@ describe('ConnectionsTable', () => { onSelect: onAddSystem, tone: 'primary', }, - { - label: 'Agent profiles', - onSelect: vi.fn(), - tone: 'secondary', - }, ]} /> ) as any); - expect(screen.getByRole('button', { name: 'Agent profiles' })).toBeInTheDocument(); const button = screen.getByRole('button', { name: /Add infrastructure/i }); fireEvent.click(button); expect(onAddSystem).toHaveBeenCalledTimes(1); diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx index bc97c9000..e9a04b199 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx @@ -168,8 +168,9 @@ describe('InfrastructureWorkspace', () => { renderWorkspace(); expect(screen.getByRole('heading', { name: 'Monitored systems' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Connections' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Agent profiles' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Connections' })).toBeNull(); + expect(screen.queryByRole('button', { name: 'Agent profiles' })).toBeNull(); expect(screen.queryByTestId('platform-section')).toBeNull(); expect(screen.queryByTestId('install-section')).toBeNull(); expect(screen.queryByTestId('agent-profiles')).toBeNull(); @@ -266,14 +267,13 @@ describe('InfrastructureWorkspace', () => { expect(screen.queryByTestId('platform-section')).toBeNull(); }); - it('opens the platform connections drawer from the header action', () => { + it('opens agent profiles from the add-infrastructure chooser secondary action', () => { renderWorkspace(); - fireEvent.click(screen.getByRole('button', { name: 'Connections' })); + fireEvent.click(screen.getByRole('button', { name: 'Add infrastructure' })); + fireEvent.click(screen.getByRole('button', { name: 'Manage agent profiles' })); - expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms', { - scroll: false, - }); + expect(screen.getByTestId('agent-profiles')).toBeInTheDocument(); }); it('closes the platform drawer back to the ledger route', () => { @@ -287,14 +287,6 @@ describe('InfrastructureWorkspace', () => { }); }); - it('opens agent profiles in a dedicated drawer instead of inline', () => { - renderWorkspace(); - - fireEvent.click(screen.getByRole('button', { name: 'Agent profiles' })); - - expect(screen.getByTestId('agent-profiles')).toBeInTheDocument(); - }); - it('collapses read-only sessions back to inventory and hides setup sections', () => { presentationPolicyIsReadOnlyMock.mockReturnValue(true); mockPathname = '/settings/infrastructure/install'; diff --git a/frontend-modern/src/components/Settings/__tests__/connectionsTableModel.test.ts b/frontend-modern/src/components/Settings/__tests__/connectionsTableModel.test.ts index 1569c9a9d..9dc056ce7 100644 --- a/frontend-modern/src/components/Settings/__tests__/connectionsTableModel.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/connectionsTableModel.test.ts @@ -61,7 +61,7 @@ describe('connectionsTableModel', () => { manageLabel: 'Review ignored', }); expect(rows.find((row) => row.name === 'tower')).toMatchObject({ - subtitle: 'Monitored system', + subtitle: undefined, manageLabel: 'View details', }); }); diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index 6c99706ee..f24486e55 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -1433,10 +1433,10 @@ describe('Settings architecture guardrails', () => { ); expect(SETTINGS_HEADER_META['infrastructure-systems'].title).toBe('Infrastructure'); expect(SETTINGS_HEADER_META['infrastructure-systems'].description).toContain( - 'open drawers for platform connections', + 'use Add infrastructure', ); expect(SETTINGS_HEADER_META['infrastructure-systems'].description).toBe( - `Review top-level monitored systems from one ledger, then open drawers for platform connections, install commands, and agent profiles. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, + `Review monitored systems in one ledger, then use Add infrastructure when you need platform setup or agent install commands. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, ); expect(SETTINGS_HEADER_META['infrastructure-connections'].title).toBe('Infrastructure'); expect(SETTINGS_HEADER_META['infrastructure-install'].title).toBe('Infrastructure'); diff --git a/frontend-modern/src/components/Settings/connectionsTableModel.ts b/frontend-modern/src/components/Settings/connectionsTableModel.ts index 27b0882d7..9001e8590 100644 --- a/frontend-modern/src/components/Settings/connectionsTableModel.ts +++ b/frontend-modern/src/components/Settings/connectionsTableModel.ts @@ -12,7 +12,7 @@ export type SystemManageAction = export interface InfrastructureSystemRow { id: string; name: string; - subtitle: string; + subtitle?: string; host?: string; coverageLabels: string[]; collectionLabel: string; @@ -65,7 +65,7 @@ const reportingRow = (row: UnifiedAgentRow): InfrastructureSystemRow => { return { id: row.rowKey, name: row.name, - subtitle: row.status === 'removed' ? 'Ignored by Pulse' : 'Monitored system', + subtitle: row.status === 'removed' ? 'Ignored by Pulse' : undefined, host: row.hostname && row.hostname !== row.name && row.hostname !== row.displayName ? row.hostname diff --git a/frontend-modern/src/components/Settings/settingsHeaderMeta.ts b/frontend-modern/src/components/Settings/settingsHeaderMeta.ts index 4d4642a8c..7160f0211 100644 --- a/frontend-modern/src/components/Settings/settingsHeaderMeta.ts +++ b/frontend-modern/src/components/Settings/settingsHeaderMeta.ts @@ -10,17 +10,17 @@ export const SETTINGS_HEADER_META: SettingsHeaderMetaMap = { 'infrastructure-systems': { title: 'Infrastructure', description: - `Review top-level monitored systems from one ledger, then open drawers for platform connections, install commands, and agent profiles. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, + `Review monitored systems in one ledger, then use Add infrastructure when you need platform setup or agent install commands. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, }, 'infrastructure-connections': { title: 'Infrastructure', description: - `Review top-level monitored systems from one ledger, then open drawers for platform connections, install commands, and agent profiles. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, + `Review monitored systems in one ledger, then use Add infrastructure when you need platform setup or agent install commands. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, }, 'infrastructure-install': { title: 'Infrastructure', description: - `Review top-level monitored systems from one ledger, then open drawers for platform connections, install commands, and agent profiles. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, + `Review monitored systems in one ledger, then use Add infrastructure when you need platform setup or agent install commands. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, }, 'system-general': { title: 'General',