mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-19 16:27:37 +00:00
Rework infrastructure setup flows as centered modals
This commit is contained in:
parent
501c61b82f
commit
b850e4fb2c
12 changed files with 105 additions and 65 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ interface AddSystemPickerProps {
|
|||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSelect: (choice: AddSystemChoice) => void;
|
||||
onManageProfiles?: () => void;
|
||||
}
|
||||
|
||||
export const AddSystemPicker: Component<AddSystemPickerProps> = (props) => {
|
||||
|
|
@ -77,10 +78,10 @@ export const AddSystemPicker: Component<AddSystemPickerProps> = (props) => {
|
|||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 id="add-system-picker-title" class="text-lg font-semibold text-base-content">
|
||||
Add a system
|
||||
Add infrastructure
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-muted">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
|
|
@ -119,6 +120,22 @@ export const AddSystemPicker: Component<AddSystemPickerProps> = (props) => {
|
|||
)}
|
||||
</For>
|
||||
</ul>
|
||||
<For each={props.onManageProfiles ? [props.onManageProfiles] : []}>
|
||||
{(onManageProfiles) => (
|
||||
<div class="flex items-center justify-between gap-3 rounded-md border border-border bg-surface-alt px-4 py-3">
|
||||
<div class="text-xs text-muted">
|
||||
Agent profiles change install defaults for agent-based systems.
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onManageProfiles}
|
||||
class="inline-flex items-center rounded-md border border-border px-3 py-1.5 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
|
||||
>
|
||||
Manage agent profiles
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -93,7 +93,9 @@ export const ConnectionsTable: Component<ConnectionsTableProps> = (props) => {
|
|||
<Show when={row.host}>
|
||||
<div class="truncate text-xs text-muted">{row.host}</div>
|
||||
</Show>
|
||||
<div class="text-xs text-muted">{row.subtitle}</div>
|
||||
<Show when={row.subtitle}>
|
||||
<div class="text-xs text-muted">{row.subtitle}</div>
|
||||
</Show>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
|
|
|
|||
|
|
@ -50,16 +50,6 @@ 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 infrastructure',
|
||||
onSelect: () => setPickerOpen(true),
|
||||
|
|
@ -164,6 +154,10 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
isOpen={pickerOpen()}
|
||||
onClose={() => setPickerOpen(false)}
|
||||
onSelect={handleAddSystem}
|
||||
onManageProfiles={() => {
|
||||
setPickerOpen(false);
|
||||
setProfilesOpen(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
<InfrastructureStopMonitoringDialog />
|
||||
|
|
@ -171,20 +165,45 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
<Dialog
|
||||
isOpen={profilesOpen()}
|
||||
onClose={() => setProfilesOpen(false)}
|
||||
layout="drawer-right"
|
||||
panelClass="max-w-[960px]"
|
||||
panelClass="h-[calc(100dvh-2rem)] w-full max-w-[1100px]"
|
||||
ariaLabel="Agent profiles"
|
||||
>
|
||||
<div class="h-full overflow-y-auto bg-surface p-4 sm:p-6">
|
||||
<AgentProfilesPanel />
|
||||
<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-lg font-semibold text-base-content">Agent profiles</div>
|
||||
<div class="text-sm text-muted">
|
||||
Manage reusable install defaults for agent-based systems.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setProfilesOpen(false)}
|
||||
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">
|
||||
<AgentProfilesPanel />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
isOpen={!readOnlyWorkspace() && activeView() === 'platforms'}
|
||||
onClose={closeConnectionsWorkspace}
|
||||
layout="drawer-right"
|
||||
panelClass="max-w-[1120px]"
|
||||
panelClass="h-[calc(100dvh-2rem)] w-full max-w-[1280px]"
|
||||
ariaLabel="Platform connections"
|
||||
>
|
||||
<div class="flex h-full flex-col bg-surface">
|
||||
|
|
@ -225,8 +244,7 @@ const InfrastructureWorkspaceContent: Component<InfrastructureWorkspaceProps> =
|
|||
<Dialog
|
||||
isOpen={!readOnlyWorkspace() && activeView() === 'install'}
|
||||
onClose={closeInstallWorkspace}
|
||||
layout="drawer-right"
|
||||
panelClass="max-w-[1120px]"
|
||||
panelClass="h-[calc(100dvh-2rem)] w-full max-w-[1280px]"
|
||||
ariaLabel="Install Pulse agent"
|
||||
>
|
||||
<div class="flex h-full flex-col bg-surface">
|
||||
|
|
|
|||
|
|
@ -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(() => (
|
||||
<AddSystemPicker
|
||||
isOpen={true}
|
||||
onClose={() => {}}
|
||||
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');
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { InfrastructureSystemRow } from '../connectionsTableModel';
|
|||
const row = (overrides: Partial<InfrastructureSystemRow> = {}): 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);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue