feat(setup): add platform-connections handoff

This commit is contained in:
rcourtman
2026-03-30 09:12:37 +01:00
parent d3d242cc50
commit 52652e2554
5 changed files with 81 additions and 2 deletions
@@ -149,7 +149,8 @@ management, and fleet control surfaces.
that route as the first-host install step, and present `Platform
connections` as the named API-backed alternative for Proxmox, TrueNAS, and
future provider integrations rather than leaving post-setup next actions
implicit.
implicit. That API-backed alternative must be a real first-run handoff
control, not prose-only guidance.
6. Keep `frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx`
oriented around the first monitored host. Install-token generation,
governed command copy, and install instructions belong to the canonical
@@ -85,6 +85,7 @@ interface ConnectedAgent {
const RELAY_SETTINGS_PATH = '/settings/system-relay';
const INFRASTRUCTURE_INSTALL_PATH = '/settings/infrastructure/install';
const INFRASTRUCTURE_PLATFORMS_PATH = '/settings/infrastructure/platforms';
const SETUP_WIZARD_TELEMETRY_SURFACE = 'setup_wizard_complete';
const pd = (resource: Resource) =>
@@ -254,6 +255,7 @@ export const SetupCompletionPanel: Component<CompleteStepProps> = (props) => {
const downloadCredentials = () => {
const baseUrl = getPulseBaseUrl();
const installWorkspaceUrl = `${baseUrl.replace(/\/$/, '')}${INFRASTRUCTURE_INSTALL_PATH}`;
const platformConnectionsUrl = `${baseUrl.replace(/\/$/, '')}${INFRASTRUCTURE_PLATFORMS_PATH}`;
const content = `Pulse Credentials
==================
Generated: ${new Date().toISOString()}
@@ -272,12 +274,20 @@ Infrastructure Install Workspace:
---------------------------------
${installWorkspaceUrl}
Platform Connections Workspace:
-------------------------------
${platformConnectionsUrl}
Use the Infrastructure Install workspace to:
- continue with the first-host install token Pulse prepares from setup
- choose the agent connection URL
- configure TLS and custom CA options
- copy Linux, macOS, Windows, and related install commands
Use the Platform connections workspace when:
- the first system is API-backed, such as Proxmox or TrueNAS
- Pulse should poll that platform directly instead of starting with a host install
Keep these credentials secure!
`;
@@ -296,6 +306,10 @@ Keep these credentials secure!
props.onComplete(INFRASTRUCTURE_INSTALL_PATH);
};
const handleOpenPlatformConnections = () => {
props.onComplete(INFRASTRUCTURE_PLATFORMS_PATH);
};
const handleGoToDashboard = () => {
props.onComplete('/');
};
@@ -664,7 +678,7 @@ Keep these credentials secure!
<div class="mt-2 text-xs text-muted">
{hasConnectedAgents()
? 'Infrastructure Install stays available any time you want to add more systems later.'
: 'Use that workspace any time you want to add more systems later.'}
: 'If the first system is API-backed, use Platform connections instead of starting with host install.'}
</div>
</div>
<div class="mt-4 flex flex-col gap-3 sm:flex-row">
@@ -676,6 +690,14 @@ Keep these credentials secure!
>
{hasConnectedAgents() ? 'Go to Dashboard' : 'Open Infrastructure Install'}
</button>
<Show when={!hasConnectedAgents()}>
<button
onClick={handleOpenPlatformConnections}
class="inline-flex items-center justify-center gap-2 rounded-md border border-border px-4 py-3 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
>
Open Platform connections
</button>
</Show>
<Show when={hasConnectedAgents()}>
<button
onClick={handleOpenInstallWorkspace}
@@ -6,15 +6,22 @@ describe('SetupCompletionPanel guardrails', () => {
expect(setupCompletionPanelSource).toContain(
"const INFRASTRUCTURE_INSTALL_PATH = '/settings/infrastructure/install';",
);
expect(setupCompletionPanelSource).toContain(
"const INFRASTRUCTURE_PLATFORMS_PATH = '/settings/infrastructure/platforms';",
);
expect(setupCompletionPanelSource).toContain('Open Infrastructure Install');
expect(setupCompletionPanelSource).toContain('Open Platform connections');
expect(setupCompletionPanelSource).toContain('Infrastructure Install Workspace');
expect(setupCompletionPanelSource).toContain('Platform Connections Workspace');
expect(setupCompletionPanelSource).toContain('Credentials you must save now');
expect(setupCompletionPanelSource).toContain('Shown during setup');
expect(setupCompletionPanelSource).toContain('props.onComplete(INFRASTRUCTURE_INSTALL_PATH);');
expect(setupCompletionPanelSource).toContain('props.onComplete(INFRASTRUCTURE_PLATFORMS_PATH);');
expect(setupCompletionPanelSource).toContain('Use the Infrastructure Install workspace to:');
expect(setupCompletionPanelSource).toContain(
'continue with the first-host install token Pulse prepares from setup',
);
expect(setupCompletionPanelSource).toContain('Use the Platform connections workspace when:');
expect(setupCompletionPanelSource).toContain('configure TLS and custom CA options');
expect(setupCompletionPanelSource).toContain('runStartProTrialAction({');
expect(setupCompletionPanelSource).not.toContain('getUpgradeActionUrlOrFallback');
@@ -34,6 +41,9 @@ describe('SetupCompletionPanel guardrails', () => {
expect(setupCompletionPanelSource).toContain(
'API-backed platforms like Proxmox and TrueNAS use Platform connections instead of a dedicated install profile in Infrastructure Install.',
);
expect(setupCompletionPanelSource).toContain(
'If the first system is API-backed, use Platform connections instead of starting with host install.',
);
expect(setupCompletionPanelSource).not.toContain('Smart Auto-Detection');
expect(setupCompletionPanelSource).not.toContain('Agent Metrics');
expect(setupCompletionPanelSource).not.toContain('ProxmoxIcon');
@@ -70,6 +80,7 @@ describe('SetupCompletionPanel guardrails', () => {
expect(setupCompletionPanelSource).toContain(
"{hasConnectedAgents() ? 'Go to Dashboard' : 'Open Infrastructure Install'}",
);
expect(setupCompletionPanelSource).toContain('handleOpenPlatformConnections');
expect(setupCompletionPanelSource).not.toContain(
"connectedAgents().length > 0 ? 'Go to Dashboard' : 'Open Infrastructure Install'",
);
@@ -110,6 +110,7 @@ describe('SetupCompletionPanel', () => {
expect(screen.getByText('What happens next')).toBeInTheDocument();
expect(screen.getAllByText('Open Infrastructure Install').length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: 'Open Platform connections' })).toBeInTheDocument();
expect(screen.getByText('Credentials you must save now')).toBeInTheDocument();
expect(screen.getByText('Shown during setup')).toBeInTheDocument();
expect(screen.getByText('admin')).toBeInTheDocument();
@@ -147,6 +148,16 @@ describe('SetupCompletionPanel', () => {
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure/install');
});
it('hands API-backed starts into the canonical platform connections workspace', async () => {
const onComplete = vi.fn();
render(() => <SetupCompletionPanel state={baseState} onComplete={onComplete} />);
fireEvent.click(screen.getByRole('button', { name: 'Open Platform connections' }));
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure/platforms');
});
it('downloads credentials that point operators to the install workspace instead of inline commands', async () => {
const anchorClickMock = vi.fn();
const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName) => {
@@ -173,9 +184,14 @@ describe('SetupCompletionPanel', () => {
expect(content).toContain('Admin API Token:');
expect(content).toContain('Infrastructure Install Workspace:');
expect(content).toContain('https://pulse.example.com/settings/infrastructure/install');
expect(content).toContain('Platform Connections Workspace:');
expect(content).toContain('https://pulse.example.com/settings/infrastructure/platforms');
expect(content).toContain(
'continue with the first-host install token Pulse prepares from setup',
);
expect(content).toContain(
'the first system is API-backed, such as Proxmox or TrueNAS',
);
expect(content).not.toContain('Example Install Command');
expect(content).not.toContain('Example Windows Install Command');
@@ -229,6 +245,7 @@ describe('SetupCompletionPanel', () => {
expect(
screen.getAllByRole('button', { name: 'Open Infrastructure Install' }).length,
).toBeGreaterThan(0);
expect(screen.queryByRole('button', { name: 'Open Platform connections' })).not.toBeInTheDocument();
const nextStepHeading = screen.getByRole('heading', { name: 'Open your first dashboard view' });
const nextStepCard = nextStepHeading.closest('div.bg-surface.rounded-md.border.border-border.p-6.text-left.mb-6');
@@ -0,0 +1,28 @@
import { test, expect } from '@playwright/test';
test.describe('Setup completion platform connections handoff', () => {
test('preview exposes Platform connections as the API-backed first-run alternative', async ({
page,
}) => {
await page.goto('/preview/setup-complete', { waitUntil: 'domcontentloaded' });
await expect(page.getByText('What happens next')).toBeVisible();
await expect(page.getByRole('button', { name: 'Open Infrastructure Install' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Open Platform connections' })).toBeVisible();
await expect(
page.getByText(
'If the first system is API-backed, use Platform connections instead of starting with host install.',
),
).toBeVisible();
await page.getByRole('button', { name: 'Open Platform connections' }).click();
await page.waitForURL(/\/settings\/infrastructure\/platforms$/, { timeout: 15_000 });
await expect(
page.getByRole('heading', { name: 'Infrastructure Operations', exact: true }),
).toBeVisible();
await expect(page.getByRole('tab', { name: 'Platform connections' })).toHaveAttribute(
'aria-selected',
'true',
);
});
});