mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Unify first-run onboarding handoff
This commit is contained in:
@@ -137,6 +137,16 @@ management, and fleet control surfaces.
|
||||
2. Keep shared API proof routing aligned whenever install, register, or profile payloads change.
|
||||
3. Update runtime and settings tests in the same slice when lifecycle behavior changes.
|
||||
4. Preserve canonical /api/auto-register node identity continuity when canonical hosts shift between hostname and IP forms for the same node.
|
||||
5. Keep first-session lifecycle handoff explicit: the live setup completion
|
||||
surface in `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`
|
||||
must route the primary CTA into `/settings/infrastructure/install`, frame
|
||||
that route as the first-host install step, and present Direct Proxmox as
|
||||
the named alternative rather than leaving post-setup next actions implicit.
|
||||
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
|
||||
lifecycle path; transport details and adjacent alternatives must remain
|
||||
secondary to that first-host onboarding narrative.
|
||||
|
||||
## Current State
|
||||
|
||||
|
||||
@@ -165,6 +165,12 @@ work extends shared components instead of creating new local variants.
|
||||
`frontend-modern/src/utils/dashboardEmptyStatePresentation.ts`, and make
|
||||
infrastructure setup guidance name the canonical destination explicitly
|
||||
instead of falling back to generic settings CTA labels.
|
||||
7. Keep the live first-session wizard on the canonical three-step runtime
|
||||
shape in `frontend-modern/src/components/SetupWizard/SetupWizard.tsx`
|
||||
(`Welcome`, `Security`, then `Install`), and keep the step indicator plus
|
||||
completion CTA language aligned with the governed infrastructure install
|
||||
workspace instead of regressing to a route jump that leaves the next action
|
||||
implicit.
|
||||
|
||||
## Current State
|
||||
|
||||
@@ -1311,16 +1317,6 @@ badge label, title, and lock-button copy, and
|
||||
the settings-shell consumer for environment-variable-locked container-update
|
||||
controls. Future environment-lock UX should extend those owners instead of
|
||||
reintroducing panel-local lock labels, badge styling, or title copy.
|
||||
First-session runtime framing is now part of that same owned primitive story.
|
||||
`frontend-modern/src/components/SetupWizard/SetupWizard.tsx` must stay on the
|
||||
real two-step runtime shape (`Welcome`, then `Security`) and hand successful
|
||||
setup directly into the canonical Infrastructure Operations install route
|
||||
instead of regrowing a second setup-only completion step. The standalone
|
||||
preview surface in
|
||||
`frontend-modern/src/components/SetupWizard/SetupCompletionPreview.tsx` may
|
||||
still exist for guarded preview coverage, but it must remain explicitly
|
||||
separate from the runtime wizard so first-session UX does not split into two
|
||||
competing product flows again.
|
||||
The release-ready shell proof now also includes a representative desktop
|
||||
Playwright rehearsal in
|
||||
`tests/integration/tests/15-settings-shell-consistency.spec.ts` so general,
|
||||
|
||||
@@ -145,6 +145,12 @@ assembly branch.
|
||||
2. Update contract and guardrail tests when a new resource type is added
|
||||
3. Route runtime changes through the explicit unified-resource proof policies in `registry.json`; default fallback proof routing is not allowed
|
||||
4. Tighten banned-path tests when a compatibility bridge is removed
|
||||
5. Keep the infrastructure landing empty state on canonical first-run routing:
|
||||
when inventory is empty, `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
|
||||
and `frontend-modern/src/utils/infrastructureEmptyStatePresentation.ts`
|
||||
must send operators directly to `/settings/infrastructure/install`, name
|
||||
first-host install as the default next step, and keep Direct Proxmox as the
|
||||
explicit alternative instead of regressing to generic settings-root CTAs.
|
||||
|
||||
## Current State
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ export const InfrastructureInstallerSection: Component = () => {
|
||||
title={state.isEmbedded() ? 'Install on a host' : 'Infrastructure'}
|
||||
description={
|
||||
state.isEmbedded()
|
||||
? 'Use the unified agent as the default path for hosts, Docker, Kubernetes, and agent-managed Proxmox.'
|
||||
: 'Primary setup hub for unified agents across systems, Docker, Kubernetes, Proxmox, and related infrastructure.'
|
||||
? 'Start here to add the first system you want Pulse to monitor, then expand into Docker, Kubernetes, Proxmox, and related infrastructure.'
|
||||
: 'Primary setup hub for installing Pulse on the first host you want to monitor, then expanding into Docker, Kubernetes, Proxmox, and related infrastructure.'
|
||||
}
|
||||
icon={<Server class="h-5 w-5" strokeWidth={2} />}
|
||||
bodyClass="space-y-5"
|
||||
@@ -112,10 +112,10 @@ export const InfrastructureInstallerSection: Component = () => {
|
||||
</Show>
|
||||
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-900 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-100">
|
||||
<p class="font-semibold">Unified Agent is the default monitoring gateway.</p>
|
||||
<p class="font-semibold">Start with the first host you want Pulse to monitor.</p>
|
||||
<p class="mt-1 text-xs text-blue-800 dark:text-blue-200">
|
||||
Install it on each system you want Pulse to monitor. The installer auto-detects
|
||||
available platforms on that machine and enables the right integrations.
|
||||
Install the Pulse agent on that system first. Once it connects, Pulse can keep using this
|
||||
workspace to add more hosts and layered platform integrations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -148,11 +148,11 @@ export const InfrastructureInstallerSection: Component = () => {
|
||||
<span class="mr-1.5 inline-flex h-5 w-5 items-center justify-center rounded-full bg-blue-600 text-xs font-bold text-white">
|
||||
1
|
||||
</span>
|
||||
Generate API token
|
||||
Generate install token
|
||||
</p>
|
||||
<p class="ml-6 text-sm text-muted">
|
||||
{state.requiresToken()
|
||||
? 'Create a fresh token scoped for Agent, Docker, and Kubernetes monitoring.'
|
||||
? 'Create a fresh token for the generated install commands and host reporting.'
|
||||
: 'Tokens are optional on this Pulse instance. Generate one if you want copied commands to preserve explicit credentialed transport.'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -190,8 +190,8 @@ export const InfrastructureInstallerSection: Component = () => {
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span>
|
||||
Token <strong>{state.latestRecord()?.name}</strong> created. Commands below now
|
||||
include this credential.
|
||||
Install token <strong>{state.latestRecord()?.name}</strong> created. Commands below
|
||||
now include this credential.
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
@@ -229,7 +229,7 @@ export const InfrastructureInstallerSection: Component = () => {
|
||||
Installation commands
|
||||
</h4>
|
||||
<p class="ml-6 mt-0.5 text-xs text-muted">
|
||||
Generate a token above to unlock installation commands.
|
||||
Generate an install token above to unlock the commands for your first host.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -247,7 +247,9 @@ export const InfrastructureInstallerSection: Component = () => {
|
||||
d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-sm text-muted">Click "Generate token" above to see installation commands</p>
|
||||
<p class="text-sm text-muted">
|
||||
Click "Generate token" above to see the install commands for your first host.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
+1
-1
@@ -605,7 +605,7 @@ describe('InfrastructureOperationsController token generation', () => {
|
||||
setupComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Generate API token')).toBeInTheDocument();
|
||||
expect(screen.getByText('Generate install token')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole('button', { name: /Generate token/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -40,30 +40,30 @@ interface CompleteStepProps {
|
||||
}
|
||||
|
||||
const UNIFIED_RESOURCE_GUIDANCE = {
|
||||
title: 'Unified Resource Inventory',
|
||||
title: 'What happens next',
|
||||
description:
|
||||
'Pulse v6 starts with the Unified Agent. Install it on a system, let Pulse create one monitored system in inventory, then enrich that same inventory with workloads and linked platforms.',
|
||||
'Pulse is now secured. Your next step is to install the Pulse agent on the first system you want to monitor, then let Pulse discover and enrich that system over time.',
|
||||
steps: [
|
||||
{
|
||||
title: 'Secure Pulse',
|
||||
description:
|
||||
'Finish first-run setup so your admin account and API access are ready for real monitoring.',
|
||||
},
|
||||
{
|
||||
title: 'Open Infrastructure Install',
|
||||
description:
|
||||
'Use the canonical install workspace to generate the right Unified Agent commands for Linux, macOS, Windows, and related platforms.',
|
||||
'Use the canonical install workspace to generate the right install token and connection settings.',
|
||||
},
|
||||
{
|
||||
title: 'Bring Systems Into Pulse',
|
||||
title: 'Copy the command for your target system',
|
||||
description:
|
||||
'Each install creates one monitored system in Pulse, then Docker, Kubernetes, Proxmox, and other context can attach to that same system.',
|
||||
'Choose Linux, macOS, Windows, or another supported target and copy the matching command.',
|
||||
},
|
||||
{
|
||||
title: 'Run it on the first host you want to monitor',
|
||||
description:
|
||||
'When the agent connects, Pulse creates your first monitored system and you can add more infrastructure from there.',
|
||||
},
|
||||
],
|
||||
inventoryFacts: [
|
||||
'One install becomes one monitored system in Pulse.',
|
||||
'Infrastructure Operations owns token generation, connection URL, TLS/CA, and platform-specific install commands.',
|
||||
'Settings no longer splits install across a separate setup-only command surface.',
|
||||
'Start with one host, then add more systems later from the same install workspace.',
|
||||
'Infrastructure Install owns the token, connection URL, TLS/CA settings, and platform-specific commands.',
|
||||
'Direct Proxmox stays available as an alternative path when you need it.',
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -334,10 +334,11 @@ Keep these credentials secure!
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-2xl sm:text-3xl font-bold tracking-tight text-base-content mb-2">
|
||||
Security Configured
|
||||
Install your first monitored host
|
||||
</h1>
|
||||
<p class="text-slate-500 dark:text-emerald-300 font-light text-sm sm:text-base">
|
||||
Open Infrastructure Install to bring your first monitored system into Pulse.
|
||||
Your admin account is ready. Next, open Infrastructure Install and run the generated
|
||||
command on the first system you want Pulse to monitor.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -417,10 +418,10 @@ Keep these credentials secure!
|
||||
<div class="rounded-md border border-border bg-surface-alt p-4">
|
||||
<div class="mb-3 flex items-center justify-between gap-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-[0.14em] text-muted">
|
||||
What Pulse Builds
|
||||
What to expect
|
||||
</div>
|
||||
<div class="rounded-sm bg-emerald-100 px-2 py-1 text-[10px] font-medium text-emerald-800 dark:bg-emerald-900 dark:text-emerald-300">
|
||||
Unified by default
|
||||
First host first
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@@ -453,17 +454,17 @@ Keep these credentials secure!
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Install Unified Agent
|
||||
Install your first host
|
||||
</h3>
|
||||
<p class="mt-2 text-xs text-muted max-w-xl">
|
||||
The canonical install flow now lives in Infrastructure Operations. Open that
|
||||
workspace to generate tokens, set the agent connection URL, configure TLS or custom
|
||||
CA options, and copy the correct install commands for Linux, macOS, Windows, and
|
||||
related platforms.
|
||||
workspace to generate an install token, set the agent connection URL, configure TLS
|
||||
or custom CA options, and copy the correct command for the first system you want
|
||||
Pulse to monitor.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-sm bg-blue-50 px-2 py-1 text-[10px] font-medium text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||
Single source of truth
|
||||
Recommended next step
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 rounded-md border border-border bg-surface-alt p-4">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component, createSignal, Show } from 'solid-js';
|
||||
import { WelcomeStep } from './steps/WelcomeStep';
|
||||
import { SecurityStep } from './steps/SecurityStep';
|
||||
import { SetupCompletionPanel } from './SetupCompletionPanel';
|
||||
import { StepIndicator } from './StepIndicator';
|
||||
|
||||
export type WizardStep = 'welcome' | 'security';
|
||||
export type WizardStep = 'welcome' | 'security' | 'completion';
|
||||
|
||||
export interface WizardState {
|
||||
username: string;
|
||||
@@ -28,7 +29,7 @@ export const SetupWizard: Component<SetupWizardProps> = (props) => {
|
||||
const [bootstrapToken, setBootstrapToken] = createSignal(props.bootstrapToken || '');
|
||||
const [isUnlocked, setIsUnlocked] = createSignal(props.isUnlocked || false);
|
||||
|
||||
const steps: WizardStep[] = ['welcome', 'security'];
|
||||
const steps: WizardStep[] = ['welcome', 'security', 'completion'];
|
||||
|
||||
const currentStepIndex = () => steps.indexOf(currentStep());
|
||||
|
||||
@@ -54,17 +55,20 @@ export const SetupWizard: Component<SetupWizardProps> = (props) => {
|
||||
props.onComplete(nextPath);
|
||||
};
|
||||
|
||||
const finishSecurityStep = () => {
|
||||
setCurrentStep('completion');
|
||||
};
|
||||
|
||||
const stepLabels = ['Unlock', 'Security', 'Install'];
|
||||
|
||||
return (
|
||||
<div class="min-h-screen bg-base flex flex-col" role="main" aria-label="Pulse Setup Wizard">
|
||||
{/* Background decoration */}
|
||||
<div class="fixed inset-0 overflow-hidden pointer-events-none" aria-hidden="true"></div>
|
||||
|
||||
{/* Step indicator - only show during security step */}
|
||||
<Show when={currentStep() === 'security'}>
|
||||
<div class="relative z-10 pt-8 px-4" role="navigation" aria-label="Setup progress">
|
||||
<StepIndicator steps={['Welcome', 'Security']} currentStep={1} />
|
||||
</div>
|
||||
</Show>
|
||||
<div class="relative z-10 pt-8 px-4" role="navigation" aria-label="Setup progress">
|
||||
<StepIndicator steps={stepLabels} currentStep={currentStepIndex()} />
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div class="flex-1 flex items-center justify-center p-4 relative z-10">
|
||||
@@ -84,10 +88,14 @@ export const SetupWizard: Component<SetupWizardProps> = (props) => {
|
||||
state={wizardState()}
|
||||
updateState={updateState}
|
||||
bootstrapToken={bootstrapToken()}
|
||||
onComplete={() => handleComplete('/settings/infrastructure/install')}
|
||||
onComplete={finishSecurityStep}
|
||||
onBack={prevStep}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Show when={currentStep() === 'completion'}>
|
||||
<SetupCompletionPanel state={wizardState()} onComplete={handleComplete} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+6
-6
@@ -17,13 +17,13 @@ describe('SetupCompletionPanel guardrails', () => {
|
||||
});
|
||||
|
||||
it('describes setup completion through the unified resource model instead of legacy install-command copy', () => {
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Unified Resource Inventory'");
|
||||
expect(setupCompletionPanelSource).toContain('Pulse v6 starts with the Unified Agent.');
|
||||
expect(setupCompletionPanelSource).toContain("title: 'What happens next'");
|
||||
expect(setupCompletionPanelSource).toContain('Pulse is now secured.');
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Open Infrastructure Install'");
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Bring Systems Into Pulse'");
|
||||
expect(setupCompletionPanelSource).toContain('What Pulse Builds');
|
||||
expect(setupCompletionPanelSource).toContain('Unified by default');
|
||||
expect(setupCompletionPanelSource).toContain('One install becomes one monitored system in Pulse.');
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Run it on the first host you want to monitor'");
|
||||
expect(setupCompletionPanelSource).toContain('What to expect');
|
||||
expect(setupCompletionPanelSource).toContain('First host first');
|
||||
expect(setupCompletionPanelSource).toContain('Start with one host, then add more systems later from the same install workspace.');
|
||||
expect(setupCompletionPanelSource).not.toContain('Smart Auto-Detection');
|
||||
expect(setupCompletionPanelSource).not.toContain('Agent Metrics');
|
||||
expect(setupCompletionPanelSource).not.toContain('ProxmoxIcon');
|
||||
|
||||
@@ -108,13 +108,13 @@ describe('SetupCompletionPanel', () => {
|
||||
it('frames setup completion around the canonical infrastructure install workspace', async () => {
|
||||
render(() => <SetupCompletionPanel state={baseState} onComplete={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText('Unified Resource Inventory')).toBeInTheDocument();
|
||||
expect(screen.getByText('What happens next')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('Open Infrastructure Install').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('What Pulse Builds')).toBeInTheDocument();
|
||||
expect(screen.getByText('Unified by default')).toBeInTheDocument();
|
||||
expect(screen.getByText('What to expect')).toBeInTheDocument();
|
||||
expect(screen.getByText('First host first')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Infrastructure Operations owns token generation, connection URL, TLS/CA, and platform-specific install commands.',
|
||||
'Infrastructure Install owns the token, connection URL, TLS/CA settings, and platform-specific commands.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
|
||||
@@ -17,8 +17,20 @@ vi.mock('../steps/SecurityStep', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../SetupCompletionPanel', () => ({
|
||||
SetupCompletionPanel: (props: { onComplete: (nextPath?: string) => void }) => (
|
||||
<button onClick={() => props.onComplete('/settings/infrastructure/install')}>
|
||||
Completion finish
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../StepIndicator', () => ({
|
||||
StepIndicator: () => <div>Step indicator</div>,
|
||||
StepIndicator: (props: { steps: string[]; currentStep: number }) => (
|
||||
<div>
|
||||
Step indicator {props.currentStep}:{props.steps.join(' > ')}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('SetupWizard', () => {
|
||||
@@ -30,15 +42,20 @@ describe('SetupWizard', () => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('sends the normal runtime path directly into infrastructure install after security setup', () => {
|
||||
it('routes first-run setup through the completion handoff before infrastructure install', () => {
|
||||
const onComplete = vi.fn();
|
||||
|
||||
render(() => <SetupWizard onComplete={onComplete} />);
|
||||
|
||||
expect(screen.getByText('Step indicator 0:Unlock > Security > Install')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Welcome next' }));
|
||||
expect(screen.getByText('Step indicator')).toBeInTheDocument();
|
||||
expect(screen.getByText('Step indicator 1:Unlock > Security > Install')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Security complete' }));
|
||||
expect(screen.getByText('Step indicator 2:Unlock > Security > Install')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Completion finish' })).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Completion finish' }));
|
||||
|
||||
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure/install');
|
||||
});
|
||||
|
||||
@@ -108,7 +108,8 @@ export const SecurityStep: Component<SecurityStepProps> = (props) => {
|
||||
<div class="p-8 border-b border-border relative z-10 text-center">
|
||||
<h2 class="text-3xl font-bold tracking-tight text-base-content">Secure Your Dashboard</h2>
|
||||
<p class="text-slate-500 dark:text-blue-200 mt-2 font-light">
|
||||
Create your root administrator account
|
||||
Create your root administrator account, then continue to install your first monitored
|
||||
host.
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-8 space-y-6 relative z-10">
|
||||
@@ -225,7 +226,7 @@ export const SecurityStep: Component<SecurityStepProps> = (props) => {
|
||||
Setting up...
|
||||
</>
|
||||
) : (
|
||||
'Create Account →'
|
||||
'Create Account & Continue →'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -111,6 +111,10 @@ export const WelcomeStep: Component<WelcomeStepProps> = (props) => {
|
||||
<p class="text-xl dark:text-blue-200 font-light animate-fade-in delay-200 max-w-md mx-auto">
|
||||
Unified infrastructure intelligence
|
||||
</p>
|
||||
<p class="mt-4 text-sm text-muted max-w-xl mx-auto animate-fade-in delay-300">
|
||||
You are about to do three things: unlock setup on this Pulse server, create your admin
|
||||
account, and install the first system you want Pulse to monitor.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Bootstrap token unlock */}
|
||||
@@ -121,7 +125,8 @@ export const WelcomeStep: Component<WelcomeStepProps> = (props) => {
|
||||
Unlock Setup
|
||||
</h3>
|
||||
<p class="text-sm text-muted mb-6">
|
||||
Run the following command on your machine to retrieve the secure bootstrap token:
|
||||
Run the following command on the machine where Pulse is installed to retrieve the
|
||||
secure bootstrap token:
|
||||
</p>
|
||||
|
||||
<div class="mb-8">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { For, Show } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { buildInfrastructureWorkspacePath } from '@/components/Settings/infrastructureWorkspaceModel';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { FilterSegmentedControl, LabeledFilterSelect } from '@/components/shared/FilterToolbar';
|
||||
@@ -119,7 +120,7 @@ export function InfrastructurePageSurface() {
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate('/settings')}
|
||||
onClick={() => navigate(buildInfrastructureWorkspacePath('install'))}
|
||||
class="inline-flex items-center gap-2 rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content shadow-sm hover:bg-slate-50"
|
||||
>
|
||||
<SettingsIcon class="h-3.5 w-3.5" />
|
||||
|
||||
@@ -40,27 +40,27 @@ describe('Infrastructure empty state', () => {
|
||||
navigateSpy.mockReset();
|
||||
});
|
||||
|
||||
it('shows empty state with Add Infrastructure button when no resources exist', async () => {
|
||||
it('shows empty state with direct install guidance when no resources exist', async () => {
|
||||
const { getByText } = render(() => <Infrastructure />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('No infrastructure resources yet')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const button = getByText('Add Infrastructure');
|
||||
const button = getByText('Open Infrastructure Install');
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button.closest('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to /settings when Add Infrastructure button is clicked', async () => {
|
||||
it('navigates to the install workspace when the empty-state button is clicked', async () => {
|
||||
const { getByText } = render(() => <Infrastructure />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Add Infrastructure')).toBeInTheDocument();
|
||||
expect(getByText('Open Infrastructure Install')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(getByText('Add Infrastructure'));
|
||||
fireEvent.click(getByText('Open Infrastructure Install'));
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure/install');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,8 +10,8 @@ describe('infrastructureEmptyStatePresentation', () => {
|
||||
expect(getInfrastructureEmptyState()).toEqual({
|
||||
title: 'No infrastructure resources yet',
|
||||
description:
|
||||
'Add Proxmox VE nodes or install the Pulse agent on your infrastructure to start monitoring.',
|
||||
actionLabel: 'Add Infrastructure',
|
||||
'Start by opening Settings → Infrastructure → Install on a host and adding the first system you want Pulse to monitor. If you prefer a direct Proxmox connection instead, use Direct Proxmox.',
|
||||
actionLabel: 'Open Infrastructure Install',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ export function getInfrastructureEmptyState() {
|
||||
return {
|
||||
title: 'No infrastructure resources yet',
|
||||
description:
|
||||
'Add Proxmox VE nodes or install the Pulse agent on your infrastructure to start monitoring.',
|
||||
actionLabel: 'Add Infrastructure',
|
||||
'Start by opening Settings → Infrastructure → Install on a host and adding the first system you want Pulse to monitor. If you prefer a direct Proxmox connection instead, use Direct Proxmox.',
|
||||
actionLabel: 'Open Infrastructure Install',
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user