From c61ea4947ae14ba668b9c449708487b89754b10e Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 5 May 2026 13:25:17 +0100 Subject: [PATCH] Make Proxmox onboarding API-first --- docs/AGENT_SECURITY.md | 2 + docs/UNIFIED_AGENT.md | 10 +- ...proxmox-api-first-onboarding-2026-05-05.md | 42 ++++ docs/release-control/v6/internal/status.json | 30 +++ .../v6/internal/subsystems/agent-lifecycle.md | 17 +- .../v6/internal/subsystems/api-contracts.md | 20 +- .../NodeModalAuthenticationSection.tsx | 2 +- .../Settings/NodeModalSetupGuideSection.tsx | 205 ++++++------------ .../InfrastructureWorkspace.test.tsx | 4 +- .../NodeModalSetupGuideSection.test.tsx | 34 +-- .../__tests__/settingsArchitecture.test.ts | 6 +- .../components/Settings/useNodeModalState.ts | 7 +- ...frastructureOnboardingPresentation.test.ts | 7 +- .../__tests__/nodeModalPresentation.test.ts | 6 +- .../infrastructureOnboardingPresentation.ts | 11 +- .../src/utils/nodeModalPresentation.ts | 8 +- 16 files changed, 214 insertions(+), 197 deletions(-) create mode 100644 docs/release-control/v6/internal/records/agent-lifecycle-proxmox-api-first-onboarding-2026-05-05.md diff --git a/docs/AGENT_SECURITY.md b/docs/AGENT_SECURITY.md index 452c6bcb39..0089c2d94d 100644 --- a/docs/AGENT_SECURITY.md +++ b/docs/AGENT_SECURITY.md @@ -60,6 +60,8 @@ If Proxmox API data is enough for your use case, prefer API-only monitoring and do not install a host agent just because the installer exists. Install agents where you need data that Proxmox cannot provide through its API, or where the data lives inside a guest/container rather than at the Proxmox node layer. +The Settings Proxmox setup flow uses this API inventory path as the default; +the host telemetry agent path is for the full-telemetry cases above. Running `pulse-agent` as a custom non-root systemd user is possible by editing the service unit, but it is not a supported full-telemetry mode today. Expect diff --git a/docs/UNIFIED_AGENT.md b/docs/UNIFIED_AGENT.md index db0d558ff3..71d4be3a24 100644 --- a/docs/UNIFIED_AGENT.md +++ b/docs/UNIFIED_AGENT.md @@ -1,17 +1,19 @@ # Pulse Unified Agent The unified agent (`pulse-agent`) combines host, Docker, and Kubernetes monitoring into a single binary. It replaces older split-agent installs with one deployment and one service for simpler operations. -Install it on each host you want Pulse to monitor. This is the primary monitoring path for infrastructure onboarding. +Install it on standalone hosts and on machines where Pulse needs full node-local telemetry. +For API-backed platforms, start with the platform connection first and add the agent only where local telemetry is needed. For Proxmox, install the agent only where you need telemetry that the Proxmox API cannot provide, such as inside-guest Docker/Podman visibility, host SMART and temperature data, local ZFS/Ceph/mdadm detail, or arbitrary mount reads. Basic Proxmox inventory and utilization can use a read-only or narrowly scoped -Proxmox API token instead. See [Agent Security](AGENT_SECURITY.md) for the -root-service trade-off, restricted-user expectations, and supply-chain +Proxmox API token instead. Settings uses that API inventory path as the +default for new PVE/PBS setup. See [Agent Security](AGENT_SECURITY.md) for +the root-service trade-off, restricted-user expectations, and supply-chain verification guidance. -> Note: For temperature monitoring, use `pulse-agent --enable-proxmox` (recommended) or SSH-based collection. The legacy sensor proxy has been removed. See `docs/TEMPERATURE_MONITORING.md`. +> Note: For agent-based temperature monitoring, use `pulse-agent --enable-proxmox` or SSH-based collection. The legacy sensor proxy has been removed. See `docs/TEMPERATURE_MONITORING.md`. ## Quick Start diff --git a/docs/release-control/v6/internal/records/agent-lifecycle-proxmox-api-first-onboarding-2026-05-05.md b/docs/release-control/v6/internal/records/agent-lifecycle-proxmox-api-first-onboarding-2026-05-05.md new file mode 100644 index 0000000000..cfc4d0cf41 --- /dev/null +++ b/docs/release-control/v6/internal/records/agent-lifecycle-proxmox-api-first-onboarding-2026-05-05.md @@ -0,0 +1,42 @@ +# Agent Lifecycle Proxmox API-First Onboarding + +Date: 2026-05-05 + +## Decision + +New Proxmox VE and Proxmox Backup Server setup defaults to the API Inventory +path. The root Pulse Agent install remains supported, but it is presented as +the Host Telemetry Agent path for full node-local telemetry rather than as the +general Proxmox default. + +## Evidence + +- `frontend-modern/src/utils/nodeModalPresentation.ts` now initializes new + PVE/PBS node setup in `auto` mode, so the setup guide opens on the API + inventory command path. +- `frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx` + labels API Inventory as the recommended least-privilege path and labels the + agent command as optional full host telemetry for SMART, temperatures, local + storage details, and agent-driven operations. +- `frontend-modern/src/utils/infrastructureOnboardingPresentation.ts` now + describes Proxmox-family onboarding as API-first with the agent optional + where node-local telemetry is needed. +- `docs/AGENT_SECURITY.md` and `docs/UNIFIED_AGENT.md` carry the same operator + guidance: start with Proxmox API inventory and install agents only where + the API cannot provide the required data. + +## Proof Commands + +Run as part of this slice: + +- `npm --prefix frontend-modern test -- --run src/components/Settings/__tests__/NodeModalSetupGuideSection.test.tsx src/utils/__tests__/nodeModalPresentation.test.ts src/utils/__tests__/infrastructureOnboardingPresentation.test.ts src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx src/components/Settings/__tests__/settingsArchitecture.test.ts` +- `npm --prefix frontend-modern run type-check` +- Playwright inspection of the Settings Infrastructure Proxmox add flow at + desktop and mobile viewports after the frontend build updated. + +## Follow-Up Guard + +Future Proxmox setup copy may still recommend a host agent for temperatures, +SMART, Docker/Podman, local storage detail, and command execution, but it must +not make the root agent install the default route for ordinary Proxmox API +inventory. diff --git a/docs/release-control/v6/internal/status.json b/docs/release-control/v6/internal/status.json index 65b4e5559b..e070cc63e7 100644 --- a/docs/release-control/v6/internal/status.json +++ b/docs/release-control/v6/internal/status.json @@ -3360,6 +3360,11 @@ "path": "docs/release-control/v6/internal/records/agent-lifecycle-ai-discovery-adapter-origin-2026-05-01.md", "kind": "file" }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/records/agent-lifecycle-proxmox-api-first-onboarding-2026-05-05.md", + "kind": "file" + }, { "repo": "pulse", "path": "docs/release-control/v6/internal/records/agent-lifecycle-root-agent-hardening-2026-05-05.md", @@ -3405,6 +3410,31 @@ "path": "frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx", "kind": "file" }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/Settings/NodeModalAuthenticationSection.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/components/Settings/useNodeModalState.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/infrastructureOnboardingPresentation.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/nodeModalPresentation.ts", + "kind": "file" + }, { "repo": "pulse", "path": "internal/agentupdate/update.go", diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index bac7526205..441a9ddb72 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -142,14 +142,14 @@ only; lifecycle surfaces must not present them as executable, dispatch them through agent-local command paths, or bypass the API fail-closed execution gate. The node setup modal boundary must keep guided setup and manual credential -submission separate. For new PVE/PBS setup, Agent Install and Direct Connection -setup-script modes are command-driven auto-registration paths; Token ID/Value +submission separate. For new PVE/PBS setup, API Inventory and Host Telemetry +Agent setup modes are command-driven auto-registration paths; Token ID/Value fields, Test Connection, and Add Node submission belong only to Manual Token Setup or existing-node edit flows. -The setup guide must also present the source strategy at action time: Agent -Install is the recommended API + Agent path, Direct Connection is the advanced -API inventory path, and Manual Token Setup is an advanced manual API-token -escape hatch. +The setup guide must also present the source strategy at action time: API +Inventory is the recommended least-privilege API path, Host Telemetry Agent is +the optional full-host-telemetry root-agent path, and Manual Token Setup is an +advanced manual API-token escape hatch. That shared monitored-system impact preview boundary also owns the disabled platform-connection lifecycle state. Once a TrueNAS or VMware setup form marks @@ -772,6 +772,11 @@ The supported full-telemetry systemd agent may still run as `root`, but in the rendered service, and generated systemd units must keep conservative sandboxing in place unless a future telemetry requirement records a narrower exception. +New Proxmox VE and Proxmox Backup Server setup must default to the API +Inventory path: the UI may recommend a root Pulse Agent only as the Host +Telemetry Agent path for temperatures, SMART, local storage detail, +agent-driven operations, or other node-local telemetry the Proxmox API cannot +provide. Generated TrueNAS CORE rc.d service scripts must give `/usr/sbin/daemon -r` a supervisor pidfile with `-P`, keep the child pid in a separate diagnostic diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index d1013f4a27..db8aca2211 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -162,13 +162,13 @@ product API routes free of maintainer commercial analytics. 30. `frontend-modern/src/utils/infrastructureSettingsPresentation.ts` shared with `agent-lifecycle`: the infrastructure settings presentation helper is both an agent lifecycle control surface and an API-backed direct-node/discovery settings boundary. 31. `internal/api/access_control_handlers.go` shared with `organization-settings`: RBAC role and user-assignment handlers are both an organization settings control surface and a canonical API payload contract boundary. The shared node setup boundary above owns the guided/manual setup split - for PVE/PBS consumers: Agent Install and Direct Connection setup-script - modes are auto-registration paths, while Token ID/Value fields, Test - Connection, and Add Node are manual-token or existing-node edit controls - only. + for PVE/PBS consumers: API Inventory and Host Telemetry Agent setup modes + are auto-registration paths, while Token ID/Value fields, Test Connection, + and Add Node are manual-token or existing-node edit controls only. That same client contract must expose the setup strategy before a token - path is chosen: Agent Install is API + Agent, Direct Connection is API - inventory, and Manual Token Setup is a manual API-token escape hatch. + path is chosen: API Inventory is the recommended least-privilege API path, + Host Telemetry Agent is the optional full-host-telemetry root-agent path, + and Manual Token Setup is a manual API-token escape hatch. The inline node credential slot must keep the visible submit sequence as `Endpoint`, `Authentication`, and `Coverage` before the API-backed setup controls. That sequence is presentation guidance for the existing setup @@ -2531,10 +2531,10 @@ masked: copy-success messaging may not tell the operator to paste a token cleanup paths may not survive in one Proxmox branch after the shared UI state has moved to hint-only handling. That same shared settings consumer must keep command-driven setup and manual -credential submission distinct. When a new PVE/PBS setup is in Agent Install or -Direct Connection setup-script mode, the settings UI must not render Token -ID/Value fields, Test Connection, or Add Node controls; those controls are only -valid for Manual Token Setup or existing-node edit flows. +credential submission distinct. When a new PVE/PBS setup is in API Inventory or +Host Telemetry Agent mode, the settings UI must not render Token ID/Value +fields, Test Connection, or Add Node controls; those controls are only valid for +Manual Token Setup or existing-node edit flows. That same shared frontend setup surface must also trim and validate the canonical `host` input before invoking `/api/setup-script` downloads, and the shared `frontend-modern/src/api/nodes.ts` helper must reject empty `host` or diff --git a/frontend-modern/src/components/Settings/NodeModalAuthenticationSection.tsx b/frontend-modern/src/components/Settings/NodeModalAuthenticationSection.tsx index 0771300086..60d4a09daf 100644 --- a/frontend-modern/src/components/Settings/NodeModalAuthenticationSection.tsx +++ b/frontend-modern/src/components/Settings/NodeModalAuthenticationSection.tsx @@ -110,7 +110,7 @@ export const NodeModalAuthenticationSection: Component
Advanced manual token path: use this only when you already created the API token - yourself. Agent Install and Direct Connection register the source automatically + yourself. API Inventory and Host Telemetry Agent register the source automatically without copying token credentials into this form.
diff --git a/frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx b/frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx index 7eb081f53f..e98b04067b 100644 --- a/frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx +++ b/frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx @@ -19,15 +19,15 @@ const getNodeSetupStrategyPresentation = ( if (setupMode === 'agent') { return { - label: 'API + Agent', - detail: `Recommended: creates the ${productLabel} API token, installs Pulse Agent, and registers the source automatically. No token fields are needed in this form.`, + label: 'Host telemetry agent', + detail: `Optional full host telemetry: creates the ${productLabel} API token, installs Pulse Agent as the supported root service, and registers the source automatically.`, }; } if (setupMode === 'auto') { return { label: 'API inventory', - detail: `Advanced: creates the ${productLabel} API token and registers the API connection, but does not install Pulse Agent for node-local telemetry.`, + detail: `Recommended least-privilege path: creates the ${productLabel} API token and registers the API connection without installing a root agent.`, }; } @@ -38,6 +38,13 @@ const getNodeSetupStrategyPresentation = ( }; }; +const setupModeButtonClass = (selected: boolean): string => + `inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md border border-transparent transition-colors ${ + selected + ? 'bg-surface text-blue-600 dark:text-blue-300 border-border shadow-sm' + : 'text-muted hover:text-blue-600 dark:hover:text-blue-300 hover:bg-surface-hover' + }`; + export const NodeModalSetupGuideSection: Component = (props) => { const { modalProps, state } = props; const setupStrategy = () => @@ -51,6 +58,34 @@ export const NodeModalSetupGuideSection: Component{setupStrategy().detail}

); + const setupModeControls = () => ( +
+ + + +
+ ); return (
@@ -74,77 +109,22 @@ export const NodeModalSetupGuideSection: Component -
- - -
- - -
- - -
-
+ {setupModeControls()}

- Recommended API + Agent setup. This single command creates the API token, - installs Pulse Agent, registers the node, and leaves Pulse waiting for the agent - check-in: + Optional full host telemetry setup. This command creates the API token, installs + the Pulse Agent root service, registers the node, and leaves Pulse waiting for + the agent check-in:

  • Creates monitoring user and API token automatically
  • Registers the node with Pulse
  • -
  • Enables temperature monitoring (no SSH required)
  • -
  • Enables Pulse Patrol automation for managing VMs/containers
  • +
  • + Adds host-local telemetry such as temperatures, SMART, ZFS, Ceph, and mdadm +
  • +
  • Enables Pulse Patrol command execution for VMs and containers

Run this command on your Proxmox VE node: @@ -217,11 +197,12 @@ export const NodeModalSetupGuideSection: Component

-
-

- Advanced API inventory path: this connects Pulse to Proxmox - without installing the unified agent, so temperature monitoring and Pulse - Patrol automation stay unavailable until an agent is installed. +

+

+ Recommended API inventory path: this connects Pulse to + Proxmox without installing a root agent. Add the host telemetry agent later + only where you need temperatures, SMART, local storage details, or + agent-driven operations.

@@ -556,76 +537,21 @@ export const NodeModalSetupGuideSection: Component -

- - -
- - -
- - -
-
+ {setupModeControls()}

- Recommended API + Agent setup. This single command creates the API token, - installs Pulse Agent, registers the server, and leaves Pulse waiting for the - agent check-in: + Optional full host telemetry setup. This command creates the API token, installs + the Pulse Agent root service, registers the server, and leaves Pulse waiting for + the agent check-in:

  • One-command setup (creates API user and token automatically)
  • -
  • Built-in temperature monitoring (no SSH required)
  • -
  • Pulse features (execute commands via Pulse Assistant)
  • +
  • + Host-local telemetry such as temperatures, SMART, services, and local disks +
  • +
  • Agent-driven commands and service telemetry when explicitly used
  • Automatic reconnection on network issues

@@ -699,11 +625,12 @@ export const NodeModalSetupGuideSection: Component

-
-

- Advanced API inventory path: this connects Pulse to Proxmox - Backup Server without installing the unified agent, so host-local telemetry - and agent-driven operations stay unavailable until an agent is installed. +

+

+ Recommended API inventory path: this connects Pulse to + Proxmox Backup Server without installing a root agent. Add the host telemetry + agent later only where you need host-local telemetry or agent-driven + operations.

diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx index 8ce56ee27e..8e52f0b061 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx @@ -298,7 +298,7 @@ describe('InfrastructureWorkspace', () => { expect(within(readiness).getByText('Discovery off')).toBeInTheDocument(); expect(within(readiness).getByRole('button', { name: /Install agents/i })).toBeInTheDocument(); expect(screen.getByText('Proxmox VE')).toBeInTheDocument(); - expect(screen.getByText('Proxmox VE').closest('tr')?.className).toContain('bg-base'); + expect(screen.getByText('Proxmox VE').closest('tr')?.className).toContain('grouped-table-row'); expect(screen.queryByText('VMware vCenter')).toBeNull(); expect(screen.queryByText('TrueNAS SCALE')).toBeNull(); expect(screen.queryByText('Standalone hosts')).toBeNull(); @@ -621,7 +621,7 @@ describe('InfrastructureWorkspace', () => { expect(within(dialog).getByText('Virtualization')).toBeInTheDocument(); expect(within(dialog).getAllByText('API inventory').length).toBeGreaterThan(0); expect(within(dialog).getByText('Agent telemetry')).toBeInTheDocument(); - expect(within(dialog).getAllByText('API + Agent').length).toBeGreaterThan(0); + expect(within(dialog).getAllByText('API first').length).toBeGreaterThan(0); expect(within(dialog).getAllByText('Unlocks').length).toBeGreaterThan(0); expect(within(dialog).getByRole('button', { name: /TrueNAS SCALE/i })).toBeInTheDocument(); }); diff --git a/frontend-modern/src/components/Settings/__tests__/NodeModalSetupGuideSection.test.tsx b/frontend-modern/src/components/Settings/__tests__/NodeModalSetupGuideSection.test.tsx index 47648f9b7b..1c37935386 100644 --- a/frontend-modern/src/components/Settings/__tests__/NodeModalSetupGuideSection.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/NodeModalSetupGuideSection.test.tsx @@ -7,7 +7,7 @@ import { NodeModalSetupGuideSection } from '../NodeModalSetupGuideSection'; const renderSetupGuide = (nodeType: NodeModalNodeType = 'pve') => { const Harness = () => { - const [setupMode, setSetupMode] = createSignal('agent'); + const [setupMode, setSetupMode] = createSignal('auto'); const updateField = vi.fn((field: string, value: string | boolean | number) => { if (field === 'setupMode') { setSetupMode(value as NodeModalSetupMode); @@ -22,7 +22,7 @@ const renderSetupGuide = (nodeType: NodeModalNodeType = 'pve') => { copyQuickSetupCommand: vi.fn(), downloadProxmoxSetupScript: vi.fn(), formData: () => ({ setupMode: setupMode(), host: '' }), - isAdvancedSetupMode: () => setupMode() === 'auto' || setupMode() === 'manual', + isAdvancedSetupMode: () => setupMode() === 'manual', loadingAgentCommand: () => false, quickSetupExpiry: () => null, quickSetupExpiryLabel: () => '', @@ -40,22 +40,30 @@ const renderSetupGuide = (nodeType: NodeModalNodeType = 'pve') => { describe('NodeModalSetupGuideSection', () => { afterEach(() => cleanup()); - it('keeps Proxmox Agent install framed as the recommended API plus Agent path', () => { + it('keeps Proxmox API inventory framed as the recommended least-privilege path', () => { renderSetupGuide('pve'); expect(screen.getByText('Source strategy')).toBeInTheDocument(); - expect(screen.getByText('API + Agent')).toBeInTheDocument(); - expect(screen.getByText(/No token fields are needed in this form/i)).toBeInTheDocument(); - expect(screen.getByText(/No token fields are needed here/i)).toBeInTheDocument(); + expect(screen.getByText('API inventory')).toBeInTheDocument(); + expect(screen.getByText(/Recommended least-privilege path/i)).toBeInTheDocument(); + expect(screen.getByText(/Recommended API inventory path/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /API Inventory/i })).toBeInTheDocument(); expect(screen.queryByText('Manual API token')).not.toBeInTheDocument(); }); - it('labels Proxmox direct and manual setup as advanced escape hatches', () => { + it('keeps the root agent path explicit as optional full host telemetry', () => { renderSetupGuide('pve'); - fireEvent.click(screen.getByRole('button', { name: /^Advanced$/i })); - expect(screen.getByText('API inventory')).toBeInTheDocument(); - expect(screen.getByText(/Advanced API inventory path/i)).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: /^Host Telemetry Agent$/i })); + + expect(screen.getByText('Host telemetry agent')).toBeInTheDocument(); + expect(screen.getAllByText(/Optional full host telemetry/i).length).toBeGreaterThan(0); + expect(screen.getAllByText(/Pulse Agent root service/i).length).toBeGreaterThan(0); + expect(screen.getByText(/temperatures, SMART, ZFS, Ceph, and mdadm/i)).toBeInTheDocument(); + }); + + it('labels manual setup as the advanced token escape hatch', () => { + renderSetupGuide('pve'); fireEvent.click(screen.getByRole('button', { name: /^Manual Token Setup$/i })); expect(screen.getByText('Manual API token')).toBeInTheDocument(); @@ -63,11 +71,11 @@ describe('NodeModalSetupGuideSection', () => { expect(screen.getByText(/Advanced escape hatch: use this only when/i)).toBeInTheDocument(); }); - it('applies the same API plus Agent strategy language to PBS', () => { + it('applies the same API-first strategy language to PBS', () => { renderSetupGuide('pbs'); - expect(screen.getByText('API + Agent')).toBeInTheDocument(); + expect(screen.getByText('API inventory')).toBeInTheDocument(); expect(screen.getByText(/creates the Proxmox Backup Server API token/i)).toBeInTheDocument(); - expect(screen.getByText(/No token fields are needed here/i)).toBeInTheDocument(); + expect(screen.getAllByText(/without installing a root agent/i).length).toBeGreaterThan(0); }); }); diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index 3c689bd700..96c9ecb628 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -530,7 +530,7 @@ describe('settings architecture guardrails', () => { expect(infrastructureOnboardingPresentationSource).toContain( 'getInfrastructureSourceStrategyPresentation', ); - expect(infrastructureOnboardingPresentationSource).toContain('API + Agent'); + expect(infrastructureOnboardingPresentationSource).toContain('API first'); expect(infrastructureOnboardingPresentationSource).toContain( 'getInfrastructureSupportSummaryBadges', ); @@ -549,10 +549,10 @@ describe('settings architecture guardrails', () => { ); expect(nodeModalAuthenticationSectionSource).toContain('Advanced manual token path'); expect(nodeModalSetupGuideSectionSource).toContain('Source strategy'); - expect(nodeModalSetupGuideSectionSource).toContain('API + Agent'); + expect(nodeModalSetupGuideSectionSource).toContain('Host telemetry agent'); expect(nodeModalSetupGuideSectionSource).toContain('API inventory'); expect(nodeModalSetupGuideSectionSource).toContain('Manual API token'); - expect(nodeModalSetupGuideSectionSource).toContain('No token fields are needed'); + expect(nodeModalSetupGuideSectionSource).toContain('Recommended least-privilege path'); expect(nodeModalStatusFooterSource).toContain('guidedSetupOnlyMode'); expect(nodeModalStateSource).toContain('data.setupMode !=='); diff --git a/frontend-modern/src/components/Settings/useNodeModalState.ts b/frontend-modern/src/components/Settings/useNodeModalState.ts index b928e8a6fc..fdc1dbde8f 100644 --- a/frontend-modern/src/components/Settings/useNodeModalState.ts +++ b/frontend-modern/src/components/Settings/useNodeModalState.ts @@ -41,8 +41,7 @@ export const useNodeModalState = (props: NodeModalProps) => { const [loadingAgentCommand, setLoadingAgentCommand] = createSignal(false); const [agentCommandError, setAgentCommandError] = createSignal(null); - const isAdvancedSetupMode = () => - formData().setupMode === 'auto' || formData().setupMode === 'manual'; + const isAdvancedSetupMode = () => formData().setupMode === 'manual'; const showTemperatureMonitoringSection = () => typeof props.temperatureMonitoringEnabled === 'boolean'; const temperatureMonitoringEnabledValue = () => props.temperatureMonitoringEnabled ?? true; @@ -92,7 +91,7 @@ export const useNodeModalState = (props: NodeModalProps) => { setAgentCommandError(copyFailureMessage); notificationStore.error(copyFailureMessage); } catch (error) { - logger.error('[Agent Install] Error:', error); + logger.error('[Host Telemetry Agent] Error:', error); const message = error instanceof Error ? error.message : 'Failed to generate install command'; setAgentCommandError(message); notificationStore.error(message); @@ -337,7 +336,7 @@ export const useNodeModalState = (props: NodeModalProps) => { data.setupMode !== 'manual' ) { const setupPath = - data.setupMode === 'agent' ? 'Pulse Agent install command' : 'setup command'; + data.setupMode === 'agent' ? 'Host Telemetry Agent command' : 'API Inventory setup command'; notificationStore.info( `Run the ${setupPath} on the host. Pulse will add the node automatically.`, ); diff --git a/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts b/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts index ae3de6c63f..f996f9cf38 100644 --- a/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts @@ -42,7 +42,8 @@ describe('infrastructureOnboardingPresentation', () => { expect(agent.catalogDescription).toContain('Low-overhead host telemetry'); expect(agent.sourceStrategy).toBe('agent'); expect(pve.sourceStrategy).toBe('api-agent'); - expect(pve.coverage).toContain('Install Pulse Agent on each node'); + expect(pve.coverage).toContain('through the Proxmox API'); + expect(pve.coverage).toContain('Install Pulse Agent only on nodes'); expect(pve.coverage).toContain('SMART data'); }); @@ -56,8 +57,8 @@ describe('infrastructureOnboardingPresentation', () => { summary: 'Pulse Agent', }); expect(getInfrastructureSourceStrategyPresentation('api-agent')).toMatchObject({ - label: 'API + Agent', - summary: 'Platform API with Pulse Agent', + label: 'API first', + summary: 'Platform API, agent optional', }); expect(getInfrastructureOnboardingProductPresentation('vmware').sourceStrategy).toBe('api'); diff --git a/frontend-modern/src/utils/__tests__/nodeModalPresentation.test.ts b/frontend-modern/src/utils/__tests__/nodeModalPresentation.test.ts index b08ee4d998..1bb7089330 100644 --- a/frontend-modern/src/utils/__tests__/nodeModalPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/nodeModalPresentation.test.ts @@ -17,18 +17,18 @@ describe('nodeModalPresentation', () => { it('returns canonical default form data per proxmox node type', () => { expect(getNodeModalDefaultFormData('pve')).toMatchObject({ authType: 'token', - setupMode: 'agent', + setupMode: 'auto', verifySSL: true, monitorPhysicalDisks: false, }); expect(getNodeModalDefaultFormData('pbs')).toMatchObject({ authType: 'token', - setupMode: 'agent', + setupMode: 'auto', verifySSL: true, }); expect(getNodeModalDefaultFormData('pmg')).toMatchObject({ authType: 'password', - setupMode: 'agent', + setupMode: 'manual', monitorMailStats: true, monitorQueues: true, }); diff --git a/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts b/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts index 08374179a6..6d93f782ce 100644 --- a/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts +++ b/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts @@ -85,10 +85,10 @@ const SOURCE_STRATEGY_PRESENTATION: Record< detail: 'Installs Pulse Agent for host telemetry, local services, Docker, and Kubernetes.', }, 'api-agent': { - label: 'API + Agent', - summary: 'Platform API with Pulse Agent', + label: 'API first', + summary: 'Platform API, agent optional', detail: - 'Starts from platform inventory and adds Pulse Agent when node-local telemetry is needed.', + 'Starts with platform API inventory and adds Pulse Agent only where node-local telemetry is needed.', }, }; @@ -130,7 +130,7 @@ const PRODUCT_PRESENTATION: Record< label: 'Proxmox VE', bestFor: 'Virtualization clusters and standalone hypervisors', coverage: - 'VMs, containers, storage, and cluster health. Install Pulse Agent on each node when you want full node-local telemetry such as temperatures and SMART data', + 'VMs, containers, storage, and cluster health through the Proxmox API. Install Pulse Agent only on nodes where you want full node-local telemetry such as temperatures and SMART data', catalogDescription: 'VMs, containers, storage, cluster health', sourceStrategy: 'api-agent', autoDetect: true, @@ -140,7 +140,8 @@ const PRODUCT_PRESENTATION: Record< pbs: { label: 'Proxmox Backup Server', bestFor: 'Backup infrastructure and protected storage', - coverage: 'Backup jobs, sync, verify, prune, GC', + coverage: + 'Backup jobs, sync, verify, prune, and GC through the Proxmox API. Install Pulse Agent only where host-local telemetry is needed', catalogDescription: 'Backup jobs, sync, verify, prune, GC', sourceStrategy: 'api-agent', autoDetect: true, diff --git a/frontend-modern/src/utils/nodeModalPresentation.ts b/frontend-modern/src/utils/nodeModalPresentation.ts index fdda0fe291..5736c5275c 100644 --- a/frontend-modern/src/utils/nodeModalPresentation.ts +++ b/frontend-modern/src/utils/nodeModalPresentation.ts @@ -41,15 +41,15 @@ export interface NodeModalTestResultPresentation { icon: NodeModalTestStatus; } -export function getNodeModalDefaultFormData( - nodeType: NodeModalNodeType, -): NodeModalFormData { +export function getNodeModalDefaultFormData(nodeType: NodeModalNodeType): NodeModalFormData { + const setupMode: NodeModalSetupMode = nodeType === 'pmg' ? 'manual' : 'auto'; + return { name: '', host: '', guestURL: '', authType: nodeType === 'pmg' ? 'password' : 'token', - setupMode: 'agent', + setupMode, user: '', password: '', tokenName: '',