mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 18:44:53 +00:00
Default infrastructure settings to install
This commit is contained in:
parent
3df9963df1
commit
2c80bfe9e2
9 changed files with 123 additions and 23 deletions
|
|
@ -147,6 +147,12 @@ management, and fleet control surfaces.
|
|||
governed command copy, and install instructions belong to the canonical
|
||||
lifecycle path; transport details and adjacent alternatives must remain
|
||||
secondary to that first-host onboarding narrative.
|
||||
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.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,12 @@ work extends shared components instead of creating new local variants.
|
|||
completion CTA language aligned with the governed infrastructure install
|
||||
workspace instead of regressing to a route jump that leaves the next action
|
||||
implicit.
|
||||
8. Keep the settings-shell infrastructure landing path aligned with that same
|
||||
first-session story. `frontend-modern/src/components/Settings/settingsNavigationModel.ts`
|
||||
must treat `/settings` and the infrastructure settings tab as the canonical
|
||||
path to `/settings/infrastructure/install`, not to reporting/control, so
|
||||
the shell does not send first-time operators to the wrong infrastructure
|
||||
subview by default.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ export const InfrastructureWorkspace: Component<InfrastructureWorkspaceProps> =
|
|||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const activeView = createMemo(() => getInfrastructureWorkspaceViewFromPath(location.pathname));
|
||||
const installPath = createMemo(() => buildInfrastructureWorkspacePath('install'));
|
||||
const directPath = createMemo(() => buildInfrastructureWorkspacePath('direct'));
|
||||
|
||||
const openView = (view: InfrastructureWorkspaceView) =>
|
||||
navigate(buildInfrastructureWorkspacePath(view));
|
||||
|
|
@ -27,12 +29,66 @@ export const InfrastructureWorkspace: Component<InfrastructureWorkspaceProps> =
|
|||
return (
|
||||
<div class="space-y-6">
|
||||
<Card padding="lg" class="rounded-xl border border-border shadow-sm">
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-base font-semibold text-base-content">Infrastructure operations</h3>
|
||||
<p class="text-sm text-muted">
|
||||
New to Pulse? Start with Install on a host to add your first monitored system.
|
||||
If you prefer a direct integration instead, use Direct Proxmox.
|
||||
</p>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-base font-semibold text-base-content">Connect your first system</h3>
|
||||
<p class="text-sm text-muted">
|
||||
Start with Install on a host to connect the first machine you want Pulse to monitor.
|
||||
If you already know you want a direct integration instead, go straight to Direct
|
||||
Proxmox.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-3 lg:grid-cols-3">
|
||||
<div class="rounded-md border border-border bg-surface px-4 py-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-muted">1. Choose path</p>
|
||||
<p class="mt-1 text-sm text-base-content">
|
||||
Install Pulse on a host first, or use Direct Proxmox if that is the system you are
|
||||
connecting.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-md border border-border bg-surface px-4 py-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-muted">
|
||||
2. Generate access
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-base-content">
|
||||
Create the install token Pulse expects for the first monitored host, then copy the
|
||||
generated command.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-md border border-border bg-surface px-4 py-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-muted">
|
||||
3. Confirm reporting
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-base-content">
|
||||
Run the command on that machine, then use Reporting & control once the first
|
||||
system starts reporting.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(installPath())}
|
||||
class={`inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors ${
|
||||
activeView() === 'install'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'border border-border bg-surface text-base-content hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{activeView() === 'install' ? 'Install on a host selected' : 'Open Install on a host'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(directPath())}
|
||||
class={`inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors ${
|
||||
activeView() === 'direct'
|
||||
? 'bg-emerald-600 text-white'
|
||||
: 'border border-border bg-surface text-base-content hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{activeView() === 'direct' ? 'Direct Proxmox selected' : 'Open Direct Proxmox'}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-muted">
|
||||
{SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ vi.mock('../ProxmoxSettingsPanel', () => ({
|
|||
describe('InfrastructureWorkspace', () => {
|
||||
beforeEach(() => {
|
||||
navigateSpy.mockReset();
|
||||
mockPathname = '/settings/infrastructure/operations';
|
||||
mockPathname = '/settings/infrastructure';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -51,23 +51,26 @@ describe('InfrastructureWorkspace', () => {
|
|||
) as any,
|
||||
);
|
||||
|
||||
it('renders the canonical subtablist', () => {
|
||||
it('defaults bare infrastructure routing to install on a host', () => {
|
||||
renderWorkspace();
|
||||
|
||||
const tablist = screen.getByRole('tablist', { name: 'Infrastructure workspace' });
|
||||
expect(tablist).toBeInTheDocument();
|
||||
expect(screen.getByText('Infrastructure operations')).toBeInTheDocument();
|
||||
expect(screen.getByText('Connect your first system')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'New to Pulse? Start with Install on a host to add your first monitored system. If you prefer a direct integration instead, use Direct Proxmox.',
|
||||
'Start with Install on a host to connect the first machine you want Pulse to monitor. If you already know you want a direct integration instead, go straight to Direct Proxmox.',
|
||||
),
|
||||
).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',
|
||||
'false',
|
||||
'true',
|
||||
);
|
||||
expect(screen.getByRole('tab', { name: 'Direct Proxmox' })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
|
|
@ -75,8 +78,9 @@ describe('InfrastructureWorkspace', () => {
|
|||
);
|
||||
expect(screen.getByRole('tab', { name: 'Reporting & control' })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true',
|
||||
'false',
|
||||
);
|
||||
expect(screen.getByTestId('unified-agents')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses the shared subtabs to switch to direct proxmox', () => {
|
||||
|
|
@ -98,12 +102,33 @@ describe('InfrastructureWorkspace', () => {
|
|||
expect(screen.getByTestId('proxmox-settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the reporting route available for established operators', () => {
|
||||
mockPathname = '/settings/infrastructure/operations';
|
||||
renderWorkspace();
|
||||
|
||||
expect(screen.getByRole('tab', { name: 'Reporting & control' })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true',
|
||||
);
|
||||
expect(screen.getByTestId('agent-profiles')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses the guided workspace actions to open install and direct paths', () => {
|
||||
renderWorkspace();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Install on a host selected' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open Direct Proxmox' }));
|
||||
|
||||
expect(navigateSpy).toHaveBeenNthCalledWith(1, '/settings/infrastructure/install');
|
||||
expect(navigateSpy).toHaveBeenNthCalledWith(2, '/settings/infrastructure/proxmox');
|
||||
});
|
||||
|
||||
it('returns to the base settings route when switching away from direct proxmox', () => {
|
||||
mockPathname = '/settings/infrastructure/proxmox';
|
||||
renderWorkspace();
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Reporting & control' }));
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Install on a host' }));
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/operations');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/install');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -480,8 +480,12 @@ describe('Settings architecture guardrails', () => {
|
|||
expect(settingsInfrastructurePanelPropsSource).toContain('getInfrastructurePanelProps');
|
||||
expect(settingsNavigationModelSource).toContain('export type SettingsTab =');
|
||||
expect(settingsNavigationModelSource).toContain('export const DEFAULT_SETTINGS_TAB');
|
||||
expect(settingsNavigationModelSource).toContain(
|
||||
"const INFRASTRUCTURE_INSTALL_PREFIX = '/settings/infrastructure/install';",
|
||||
);
|
||||
expect(settingsNavigationModelSource).toContain('export function resolveCanonicalSettingsPath');
|
||||
expect(settingsNavigationModelSource).toContain('export function settingsTabPath');
|
||||
expect(settingsNavigationModelSource).toContain("return INFRASTRUCTURE_INSTALL_PREFIX;");
|
||||
expect(settingsNavigationHookSource).toContain('deriveTabFromPath');
|
||||
expect(settingsNavigationHookSource).toContain('resolveCanonicalSettingsPath');
|
||||
expect(settingsNavigationHookSource).toContain('settingsTabPath');
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { updateDisableLocalUpgradeMetricsSetting } from '@/stores/systemSettings
|
|||
|
||||
const canonicalTabPaths = {
|
||||
proxmox: '/settings/infrastructure/proxmox',
|
||||
'infrastructure-operations': '/settings/infrastructure/operations',
|
||||
'infrastructure-operations': '/settings/infrastructure/install',
|
||||
'system-general': '/settings/system-general',
|
||||
'system-network': '/settings/system-network',
|
||||
'system-updates': '/settings/system-updates',
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { isFeatureLocked, isTabLocked } from '../settingsFeatureGates';
|
|||
|
||||
const canonicalTabPaths = {
|
||||
proxmox: '/settings/infrastructure/proxmox',
|
||||
'infrastructure-operations': '/settings/infrastructure/operations',
|
||||
'infrastructure-operations': '/settings/infrastructure/install',
|
||||
'system-general': '/settings/system-general',
|
||||
'system-network': '/settings/system-network',
|
||||
'system-updates': '/settings/system-updates',
|
||||
|
|
@ -61,18 +61,18 @@ describe('settingsNavigationModel', () => {
|
|||
});
|
||||
|
||||
it('resolves only canonical settings paths', () => {
|
||||
expect(resolveCanonicalSettingsPath('/settings')).toBe('/settings/infrastructure/operations');
|
||||
expect(resolveCanonicalSettingsPath('/settings')).toBe('/settings/infrastructure/install');
|
||||
expect(resolveCanonicalSettingsPath('/settings/workloads')).toBe(
|
||||
'/settings/infrastructure/operations',
|
||||
'/settings/infrastructure/install',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/workloads/docker')).toBe(
|
||||
'/settings/infrastructure/operations',
|
||||
'/settings/infrastructure/install',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/system-updates')).toBe(
|
||||
'/settings/system-updates',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure')).toBe(
|
||||
'/settings/infrastructure/operations',
|
||||
'/settings/infrastructure/install',
|
||||
);
|
||||
expect(resolveCanonicalSettingsPath('/settings/infrastructure/pve')).toBe(
|
||||
'/settings/infrastructure/proxmox/pve',
|
||||
|
|
|
|||
|
|
@ -30,10 +30,13 @@ export function getInfrastructureWorkspaceViewFromPath(
|
|||
if (pathname.startsWith('/settings/infrastructure/proxmox')) {
|
||||
return 'direct';
|
||||
}
|
||||
if (pathname.startsWith('/settings/infrastructure/operations')) {
|
||||
return 'inventory';
|
||||
}
|
||||
if (pathname.startsWith('/settings/infrastructure/install')) {
|
||||
return 'install';
|
||||
}
|
||||
return 'inventory';
|
||||
return 'install';
|
||||
}
|
||||
|
||||
export function buildInfrastructureWorkspacePath(
|
||||
|
|
@ -41,6 +44,6 @@ export function buildInfrastructureWorkspacePath(
|
|||
): string {
|
||||
return (
|
||||
INFRASTRUCTURE_WORKSPACE_TABS.find((tab) => tab.id === view)?.path ??
|
||||
'/settings/infrastructure/operations'
|
||||
'/settings/infrastructure/install'
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ export function settingsTabPath(tab: SettingsTab): string {
|
|||
case 'proxmox':
|
||||
return PROXMOX_PREFIX;
|
||||
case 'infrastructure-operations':
|
||||
return INFRASTRUCTURE_OPERATIONS_PREFIX;
|
||||
return INFRASTRUCTURE_INSTALL_PREFIX;
|
||||
case 'system-recovery':
|
||||
return '/settings/system-recovery';
|
||||
case 'organization-overview':
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue