mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-20 01:01:20 +00:00
Redesign infrastructure settings around one ledger
This commit is contained in:
parent
1c2b195d51
commit
f754dfa9b9
10 changed files with 220 additions and 103 deletions
|
|
@ -858,19 +858,21 @@ 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 also replace the workspace body
|
||||
rather than stacking beneath the systems ledger. When the operator opens
|
||||
platform connection management, install tooling, or the legacy operations
|
||||
workspace route, `InfrastructureWorkspace.tsx` must not keep the systems table
|
||||
mounted above that second surface.
|
||||
That infrastructure surface must now stay single-purpose per workspace route:
|
||||
the default systems view owns the top-level monitored-system ledger, the
|
||||
connections view owns API-backed platform management, and the install view
|
||||
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 the primary workspace subtabs rendered inside
|
||||
`InfrastructureWorkspace.tsx`, and selecting one subtab must replace the body
|
||||
instead of stacking another surface underneath it.
|
||||
Those secondary infrastructure views must open through route-backed drawers
|
||||
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.
|
||||
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
|
||||
Settings sidebar owns only the top-level `Infrastructure` destination; movement
|
||||
between those three jobs belongs to explicit drawer actions inside
|
||||
`InfrastructureWorkspace.tsx`, not extra sidebar entries or body-replacing
|
||||
workspace subtabs.
|
||||
That same lifecycle-owned platform-connections workspace must keep API-backed
|
||||
provider state operationally useful, not CRUD-only. `TrueNASSettingsPanel.tsx`
|
||||
and `useTrueNASSettingsPanelState.ts` must surface the shared runtime health,
|
||||
|
|
|
|||
|
|
@ -222,14 +222,14 @@ work extends shared components instead of creating new local variants.
|
|||
workspaces, and profile management remain secondary flows opened by explicit
|
||||
deep links or drawers instead of being dumped inline underneath the default
|
||||
table.
|
||||
Those deep-linked secondary views must replace the body below the shared
|
||||
page header instead of keeping the systems ledger mounted above them, so the
|
||||
same system or connection context is not duplicated once in the default
|
||||
ledger and again inside a secondary management workspace.
|
||||
Those deep-linked secondary views must keep the systems ledger mounted and
|
||||
open inside route-backed drawers 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.
|
||||
That same shared shell boundary now owns one canonical infrastructure
|
||||
destination in the Settings sidebar. `InfrastructureWorkspace.tsx` owns the
|
||||
primary `Systems`, `Connections`, and `Install` workspace subtabs inside
|
||||
that destination, while each selected route body still stays
|
||||
one default ledger plus route-backed `Connections` and `Install` drawers
|
||||
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.
|
||||
7. Keep shared storage feature presenters on canonical platform truth. When reusable storage presenters under `frontend-modern/src/features/storageBackups/` classify canonical resources for the shared storage route, API-backed virtualization datastores such as VMware must stay inventory-only datastores instead of inheriting PBS-specific backup-repository or protected-target copy from older fallback branches.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const ConnectionsTable: Component<ConnectionsTableProps> = (props) => {
|
|||
<Card padding="none" tone="card" class="rounded-md">
|
||||
<div class="flex flex-col gap-3 border-b border-border px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-base-content">Systems</h3>
|
||||
<h3 class="text-base font-semibold text-base-content">Monitored systems</h3>
|
||||
<p class="text-xs text-muted">One row per top-level monitored system.</p>
|
||||
</div>
|
||||
<Show when={(props.headerActions?.length ?? 0) > 0}>
|
||||
|
|
|
|||
|
|
@ -314,11 +314,11 @@ export const InfrastructureActiveRowDetails: Component<InfrastructureActiveRowDe
|
|||
data-row-action
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
navigate(path());
|
||||
navigate(path(), { scroll: false });
|
||||
}}
|
||||
class="inline-flex min-h-9 items-center rounded-md px-2.5 py-1.5 text-xs font-medium text-blue-600 hover:bg-blue-50 hover:text-blue-900 dark:text-blue-400 dark:hover:bg-blue-900 dark:hover:text-blue-300"
|
||||
>
|
||||
Open platform connections
|
||||
Manage connection
|
||||
</button>
|
||||
)}
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, Match, Show, Switch, createEffect, createMemo, createSignal } from 'solid-js';
|
||||
import { Component, Show, createEffect, createMemo, createSignal } from 'solid-js';
|
||||
import { useLocation, useNavigate } from '@solidjs/router';
|
||||
import { Dialog } from '@/components/shared/Dialog';
|
||||
import { Subtabs } from '@/components/shared/Subtabs';
|
||||
import { presentationPolicyIsReadOnly } from '@/stores/sessionPresentationPolicy';
|
||||
import { AgentProfilesPanel } from './AgentProfilesPanel';
|
||||
import { AddSystemPicker, type AddSystemChoice } from './AddSystemPicker';
|
||||
|
|
@ -19,7 +18,6 @@ import { PlatformConnectionsWorkspace } from './PlatformConnectionsWorkspace';
|
|||
import {
|
||||
buildInfrastructureWorkspacePath,
|
||||
getInfrastructureWorkspaceViewFromPath,
|
||||
type InfrastructureWorkspaceView,
|
||||
} from './infrastructureWorkspaceModel';
|
||||
import type { InfrastructurePlatformSettingsProps } from './proxmoxSettingsModel';
|
||||
import {
|
||||
|
|
@ -52,23 +50,36 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
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 a system',
|
||||
label: 'Add infrastructure',
|
||||
onSelect: () => setPickerOpen(true),
|
||||
tone: 'primary' as const,
|
||||
},
|
||||
],
|
||||
);
|
||||
const workspaceTabs = createMemo(() => [
|
||||
{ value: 'inventory', label: 'Systems' },
|
||||
{ value: 'platforms', label: 'Connections' },
|
||||
{ value: 'install', label: 'Install' },
|
||||
]);
|
||||
const closeConnectionsWorkspace = () => {
|
||||
props.trueNASSettings.closeDialog?.();
|
||||
props.trueNASSettings.closeDeleteDialog?.();
|
||||
props.vmwareSettings.closeDialog?.();
|
||||
props.vmwareSettings.closeDeleteDialog?.();
|
||||
props.setShowNodeModal(false);
|
||||
props.setEditingNode(null);
|
||||
props.cancelDeleteNode?.();
|
||||
navigate(buildInfrastructureWorkspacePath('inventory'), { scroll: false });
|
||||
};
|
||||
const closeInstallWorkspace = () => {
|
||||
navigate(buildInfrastructureWorkspacePath('inventory'), { scroll: false });
|
||||
};
|
||||
|
||||
const openProxmoxNode = (nodeKind: 'pve' | 'pbs' | 'pmg', nodeId: string) => {
|
||||
const nodes =
|
||||
|
|
@ -84,26 +95,26 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
props.setEditingNode(node);
|
||||
props.setModalResetKey((value) => value + 1);
|
||||
props.setShowNodeModal(true);
|
||||
navigate(proxmoxRouteForKind(nodeKind));
|
||||
navigate(proxmoxRouteForKind(nodeKind), { scroll: false });
|
||||
};
|
||||
|
||||
const handleAddSystem = (choice: AddSystemChoice) => {
|
||||
setPickerOpen(false);
|
||||
|
||||
if (choice.kind === 'agent') {
|
||||
navigate(buildInfrastructureWorkspacePath('install'));
|
||||
navigate(buildInfrastructureWorkspacePath('install'), { scroll: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (choice.kind === 'truenas') {
|
||||
props.trueNASSettings.openCreateDialog();
|
||||
navigate('/settings/infrastructure/platforms/truenas');
|
||||
navigate('/settings/infrastructure/platforms/truenas', { scroll: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (choice.kind === 'vmware') {
|
||||
props.vmwareSettings.openCreateDialog();
|
||||
navigate('/settings/infrastructure/platforms/vmware');
|
||||
navigate('/settings/infrastructure/platforms/vmware', { scroll: false });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -131,26 +142,23 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (activeView() === 'inventory') {
|
||||
return;
|
||||
}
|
||||
setPickerOpen(false);
|
||||
setProfilesOpen(false);
|
||||
state.setExpandedRowKey(null);
|
||||
state.setSelectedIgnoredRowKey(null);
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="space-y-8">
|
||||
<Show when={!readOnlyWorkspace()}>
|
||||
<Subtabs
|
||||
value={activeView()}
|
||||
onChange={(value) =>
|
||||
navigate(buildInfrastructureWorkspacePath(value as InfrastructureWorkspaceView))
|
||||
}
|
||||
ariaLabel="Infrastructure workspace"
|
||||
tabs={workspaceTabs()}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Show when={activeView() === 'inventory'}>
|
||||
<ConnectionsTable
|
||||
rows={rows}
|
||||
headerActions={headerActions()}
|
||||
onManageRow={(row) => handleManageAction(row.manage)}
|
||||
/>
|
||||
</Show>
|
||||
<ConnectionsTable
|
||||
rows={rows}
|
||||
headerActions={headerActions()}
|
||||
onManageRow={(row) => handleManageAction(row.manage)}
|
||||
/>
|
||||
|
||||
<AddSystemPicker
|
||||
isOpen={pickerOpen()}
|
||||
|
|
@ -172,6 +180,91 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
isOpen={!readOnlyWorkspace() && activeView() === 'platforms'}
|
||||
onClose={closeConnectionsWorkspace}
|
||||
layout="drawer-right"
|
||||
panelClass="max-w-[1120px]"
|
||||
ariaLabel="Platform connections"
|
||||
>
|
||||
<div class="flex h-full flex-col bg-surface">
|
||||
<div class="border-b border-border bg-surface-alt px-4 py-4 sm:px-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<div class="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted">
|
||||
Connections
|
||||
</div>
|
||||
<div class="text-lg font-semibold text-base-content">Platform connections</div>
|
||||
<div class="text-sm text-muted">
|
||||
Configure API-backed providers without leaving the infrastructure ledger.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeConnectionsWorkspace}
|
||||
class="rounded-md p-1 hover:bg-surface-hover hover:text-base-content"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto p-4 sm:p-6">
|
||||
<PlatformConnectionsWorkspace {...props} />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
isOpen={!readOnlyWorkspace() && activeView() === 'install'}
|
||||
onClose={closeInstallWorkspace}
|
||||
layout="drawer-right"
|
||||
panelClass="max-w-[1120px]"
|
||||
ariaLabel="Install Pulse agent"
|
||||
>
|
||||
<div class="flex h-full flex-col bg-surface">
|
||||
<div class="border-b border-border bg-surface-alt px-4 py-4 sm:px-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<div class="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted">
|
||||
Install
|
||||
</div>
|
||||
<div class="text-lg font-semibold text-base-content">Install Pulse agent</div>
|
||||
<div class="text-sm text-muted">
|
||||
Generate Linux, macOS, FreeBSD, and Windows install commands from the same
|
||||
workspace.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeInstallWorkspace}
|
||||
class="rounded-md p-1 hover:bg-surface-hover hover:text-base-content"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto p-4 sm:p-6">
|
||||
<InfrastructureInstallerSection />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
isOpen={Boolean(state.selectedActiveRow())}
|
||||
onClose={() => state.setExpandedRowKey(null)}
|
||||
|
|
@ -195,16 +288,6 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
{(rowAccessor) => <InfrastructureIgnoredRowDetails rowAccessor={rowAccessor} />}
|
||||
</Show>
|
||||
</Dialog>
|
||||
|
||||
<Switch>
|
||||
<Match when={!readOnlyWorkspace() && activeView() === 'platforms'}>
|
||||
<PlatformConnectionsWorkspace {...props} />
|
||||
</Match>
|
||||
|
||||
<Match when={!readOnlyWorkspace() && activeView() === 'install'}>
|
||||
<InfrastructureInstallerSection />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ describe('ConnectionsTable', () => {
|
|||
rows={() => []}
|
||||
headerActions={[
|
||||
{
|
||||
label: '+ Add a system',
|
||||
label: 'Add infrastructure',
|
||||
onSelect: onAddSystem,
|
||||
tone: 'primary',
|
||||
},
|
||||
|
|
@ -83,7 +83,7 @@ describe('ConnectionsTable', () => {
|
|||
) as any);
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Agent profiles' })).toBeInTheDocument();
|
||||
const button = screen.getByRole('button', { name: /\+ Add a system/i });
|
||||
const button = screen.getByRole('button', { name: /Add infrastructure/i });
|
||||
fireEvent.click(button);
|
||||
expect(onAddSystem).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1231,7 +1231,7 @@ describe('InfrastructureOperationsController managed agents table', () => {
|
|||
expect(details.getByText('Docker runtime ID')).toBeInTheDocument();
|
||||
expect(details.getByText('Node ID')).toBeInTheDocument();
|
||||
expect(details.getAllByText('delly-resource').length).toBeGreaterThan(0);
|
||||
expect(details.getByRole('button', { name: /Open platform connections/i })).toBeInTheDocument();
|
||||
expect(details.getByRole('button', { name: /Manage connection/i })).toBeInTheDocument();
|
||||
expect(details.getAllByText('delly-agent').length).toBeGreaterThan(0);
|
||||
expect(details.getAllByText('delly-docker').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
|
@ -1282,11 +1282,13 @@ describe('InfrastructureOperationsController managed agents table', () => {
|
|||
).not.toBeInTheDocument();
|
||||
|
||||
const openPlatformConnectionsButton = details.getByRole('button', {
|
||||
name: /Open platform connections/i,
|
||||
name: /Manage connection/i,
|
||||
});
|
||||
fireEvent.click(openPlatformConnectionsButton);
|
||||
|
||||
expect(navigateMock).toHaveBeenCalledWith('/settings/infrastructure/platforms/truenas');
|
||||
expect(navigateMock).toHaveBeenCalledWith('/settings/infrastructure/platforms/truenas', {
|
||||
scroll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('distinguishes PBS coverage from Proxmox nodes on non-Proxmox hosts', async () => {
|
||||
|
|
|
|||
|
|
@ -48,10 +48,6 @@ vi.mock('../useInfrastructureOperationsState', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../InfrastructureInventorySection', () => ({
|
||||
InfrastructureInventorySection: () => <div data-testid="inventory-section">inventory</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../InfrastructureInstallerSection', () => ({
|
||||
InfrastructureInstallerSection: () => <div data-testid="install-section">install</div>,
|
||||
}));
|
||||
|
|
@ -121,11 +117,15 @@ const baseProps = () =>
|
|||
trueNASSettings: {
|
||||
connections: () => [{ id: 'tn-1', name: 'Tower NAS', host: '10.0.0.20', enabled: true }],
|
||||
openCreateDialog: trueNASOpenCreateDialogSpy,
|
||||
closeDialog: vi.fn(),
|
||||
closeDeleteDialog: vi.fn(),
|
||||
openEditDialog: vi.fn(),
|
||||
},
|
||||
vmwareSettings: {
|
||||
connections: () => [{ id: 'vm-1', name: 'lab-vcenter', host: '10.0.0.30', enabled: true }],
|
||||
openCreateDialog: vmwareOpenCreateDialogSpy,
|
||||
closeDialog: vi.fn(),
|
||||
closeDeleteDialog: vi.fn(),
|
||||
openEditDialog: vi.fn(),
|
||||
},
|
||||
selectedAgent: () => 'pve',
|
||||
|
|
@ -167,10 +167,9 @@ describe('InfrastructureWorkspace', () => {
|
|||
it('renders only the top-level ledger at the base infrastructure route', () => {
|
||||
renderWorkspace();
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'Systems' })).toBeInTheDocument();
|
||||
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.queryByRole('button', { name: 'Manage connections' })).toBeNull();
|
||||
expect(screen.queryByTestId('inventory-section')).toBeNull();
|
||||
expect(screen.queryByTestId('platform-section')).toBeNull();
|
||||
expect(screen.queryByTestId('install-section')).toBeNull();
|
||||
expect(screen.queryByTestId('agent-profiles')).toBeNull();
|
||||
|
|
@ -188,7 +187,7 @@ describe('InfrastructureWorkspace', () => {
|
|||
it('opens the add-system picker when the add button is clicked', () => {
|
||||
renderWorkspace();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /\+ Add a system/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add infrastructure/i }));
|
||||
|
||||
expect(screen.getByText('Install on a host')).toBeInTheDocument();
|
||||
expect(screen.getByText('Proxmox VE')).toBeInTheDocument();
|
||||
|
|
@ -197,37 +196,45 @@ describe('InfrastructureWorkspace', () => {
|
|||
|
||||
it('routes the agent-host choice to the install section deep link', () => {
|
||||
renderWorkspace();
|
||||
fireEvent.click(screen.getByRole('button', { name: /\+ Add a system/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add infrastructure/i }));
|
||||
fireEvent.click(screen.getByText('Install on a host'));
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/install');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/install', {
|
||||
scroll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('opens provider creation flows directly from the add-system picker', () => {
|
||||
renderWorkspace();
|
||||
fireEvent.click(screen.getByRole('button', { name: /\+ Add a system/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add infrastructure/i }));
|
||||
fireEvent.click(screen.getByText('TrueNAS SCALE'));
|
||||
|
||||
expect(trueNASOpenCreateDialogSpy).toHaveBeenCalledTimes(1);
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/truenas');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/truenas', {
|
||||
scroll: false,
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /\+ Add a system/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add infrastructure/i }));
|
||||
fireEvent.click(screen.getByText('VMware vSphere or ESXi'));
|
||||
|
||||
expect(vmwareOpenCreateDialogSpy).toHaveBeenCalledTimes(1);
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/vmware');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/vmware', {
|
||||
scroll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('opens the proxmox node modal directly from the add-system picker', () => {
|
||||
renderWorkspace();
|
||||
fireEvent.click(screen.getByRole('button', { name: /\+ Add a system/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add infrastructure/i }));
|
||||
fireEvent.click(screen.getByText('Proxmox VE'));
|
||||
|
||||
expect(onSelectAgentSpy).toHaveBeenCalledWith('pve');
|
||||
expect(setCurrentNodeTypeSpy).toHaveBeenCalledWith('pve');
|
||||
expect(setEditingNodeSpy).toHaveBeenCalledWith(null);
|
||||
expect(setShowNodeModalSpy).toHaveBeenCalledWith(true);
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/proxmox/pve');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms/proxmox/pve', {
|
||||
scroll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('opens reporting details from the top ledger in a drawer', () => {
|
||||
|
|
@ -239,28 +246,47 @@ describe('InfrastructureWorkspace', () => {
|
|||
expect(screen.getByTestId('active-details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows only platform setup below the ledger on platform deep links', () => {
|
||||
it('keeps the ledger visible and opens platform setup in a drawer on platform deep links', () => {
|
||||
mockPathname = '/settings/infrastructure/platforms/truenas';
|
||||
renderWorkspace();
|
||||
|
||||
expect(screen.queryByRole('heading', { name: 'Systems' })).toBeNull();
|
||||
expect(screen.getByRole('heading', { name: 'Monitored systems' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Platform connections')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('platform-section')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('inventory-section')).toBeNull();
|
||||
expect(screen.queryByTestId('install-section')).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: 'Agent profiles' })).toBeNull();
|
||||
expect(screen.queryByTestId('agent-profiles')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows only install tools below the ledger on install deep links', () => {
|
||||
it('keeps the ledger visible and opens install tools in a drawer on install deep links', () => {
|
||||
mockPathname = '/settings/infrastructure/install';
|
||||
renderWorkspace();
|
||||
|
||||
expect(screen.queryByRole('heading', { name: 'Systems' })).toBeNull();
|
||||
expect(screen.getByRole('heading', { name: 'Monitored systems' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Install Pulse agent')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('install-section')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('inventory-section')).toBeNull();
|
||||
expect(screen.queryByTestId('platform-section')).toBeNull();
|
||||
});
|
||||
|
||||
it('opens the platform connections drawer from the header action', () => {
|
||||
renderWorkspace();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Connections' }));
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/platforms', {
|
||||
scroll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('closes the platform drawer back to the ledger route', () => {
|
||||
mockPathname = '/settings/infrastructure/platforms/truenas';
|
||||
renderWorkspace();
|
||||
|
||||
fireEvent.click(screen.getAllByRole('button', { name: 'Close' })[0]);
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure', {
|
||||
scroll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('opens agent profiles in a dedicated drawer instead of inline', () => {
|
||||
renderWorkspace();
|
||||
|
||||
|
|
@ -275,11 +301,11 @@ describe('InfrastructureWorkspace', () => {
|
|||
renderWorkspace();
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure', { replace: true });
|
||||
expect(screen.queryByRole('button', { name: /\+ Add a system/i })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /Add infrastructure/i })).toBeNull();
|
||||
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();
|
||||
expect(screen.queryByTestId('inventory-section')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -898,11 +898,15 @@ describe('Settings architecture guardrails', () => {
|
|||
expect(infrastructureWorkspaceSource).toContain('InfrastructureStopMonitoringDialog');
|
||||
expect(infrastructureWorkspaceSource).toContain('AgentProfilesPanel');
|
||||
expect(infrastructureWorkspaceSource).toContain('PlatformConnectionsWorkspace');
|
||||
expect(infrastructureWorkspaceSource).toContain("import { Subtabs } from '@/components/shared/Subtabs'");
|
||||
expect(infrastructureWorkspaceSource).toContain('<Subtabs');
|
||||
expect(infrastructureWorkspaceSource).not.toContain(
|
||||
"import { Subtabs } from '@/components/shared/Subtabs'",
|
||||
);
|
||||
expect(infrastructureWorkspaceSource).not.toContain('<Subtabs');
|
||||
expect(infrastructureWorkspaceSource).toContain('Platform connections');
|
||||
expect(infrastructureWorkspaceSource).toContain('Install Pulse agent');
|
||||
expect(infrastructureWorkspaceSource).toContain('activeView() === \'platforms\'');
|
||||
expect(infrastructureWorkspaceSource).toContain('activeView() === \'install\'');
|
||||
expect(infrastructureWorkspaceSource).not.toContain("activeView() === 'operations'");
|
||||
expect(infrastructureWorkspaceSource).toContain("activeView() === 'platforms'");
|
||||
expect(infrastructureWorkspaceSource).toContain("activeView() === 'install'");
|
||||
expect(connectionsTableSource).toContain('headerActions');
|
||||
expect(connectionsTableModelSource).toContain('export function buildInfrastructureSystemRows');
|
||||
expect(platformConnectionsWorkspaceSource).toContain('./platformConnectionsModel');
|
||||
|
|
@ -1429,10 +1433,10 @@ describe('Settings architecture guardrails', () => {
|
|||
);
|
||||
expect(SETTINGS_HEADER_META['infrastructure-systems'].title).toBe('Infrastructure');
|
||||
expect(SETTINGS_HEADER_META['infrastructure-systems'].description).toContain(
|
||||
'manage platform connections',
|
||||
'open drawers for platform connections',
|
||||
);
|
||||
expect(SETTINGS_HEADER_META['infrastructure-systems'].description).toBe(
|
||||
`Review monitored systems, manage platform connections, and install Pulse agents from one workspace. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`,
|
||||
`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}`,
|
||||
);
|
||||
expect(SETTINGS_HEADER_META['infrastructure-connections'].title).toBe('Infrastructure');
|
||||
expect(SETTINGS_HEADER_META['infrastructure-install'].title).toBe('Infrastructure');
|
||||
|
|
|
|||
|
|
@ -10,17 +10,17 @@ export const SETTINGS_HEADER_META: SettingsHeaderMetaMap = {
|
|||
'infrastructure-systems': {
|
||||
title: 'Infrastructure',
|
||||
description:
|
||||
`Review monitored systems, manage platform connections, and install Pulse agents from one workspace. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`,
|
||||
`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}`,
|
||||
},
|
||||
'infrastructure-connections': {
|
||||
title: 'Infrastructure',
|
||||
description:
|
||||
`Review monitored systems, manage platform connections, and install Pulse agents from one workspace. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`,
|
||||
`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}`,
|
||||
},
|
||||
'infrastructure-install': {
|
||||
title: 'Infrastructure',
|
||||
description:
|
||||
`Review monitored systems, manage platform connections, and install Pulse agents from one workspace. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`,
|
||||
`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}`,
|
||||
},
|
||||
'system-general': {
|
||||
title: 'General',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue