From d39a935f86ea83d2d7a82e4a71aa66b05c84e39a Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 20 Apr 2026 15:40:28 +0100 Subject: [PATCH] infra: teach the agent as a host-level add-on, not a sixth platform tile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flat catalog put Install Pulse Agent beside Proxmox / VMware / TrueNAS as a peer. A user arriving to connect Proxmox picks Proxmox instantly and never learns the agent also adds CPU temperature, disk SMART, systemd services, and Docker/Kubernetes auto-detect on the host itself. Split the landing into two sections: - "Or pick a platform to connect" — five peer tiles for PVE / PBS / PMG / VMware / TrueNAS. These are management-API integrations and belong together. - "Or install a host-level agent" — a distinct full-width card with a "Runs on a host" chip. Copy names the telemetry the agent adds and says explicitly that it layers *in addition to* a platform connection above, not instead of one. The agent is a different kind of integration from the platform APIs. Framing it as a peer hid its value; framing it as an adjunct teaches it. --- .../ConnectionEditor/ConnectionEditor.tsx | 140 ++++++++++++------ .../__tests__/settingsArchitecture.test.ts | 7 +- 2 files changed, 97 insertions(+), 50 deletions(-) diff --git a/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx b/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx index 0fdc1c324..d8b978103 100644 --- a/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx +++ b/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx @@ -1,5 +1,5 @@ import { Component, For, type JSX, Show, createMemo, createSignal } from 'solid-js'; -import { Archive, Cpu, Database, Mail, Server, ServerCog } from 'lucide-solid'; +import { Archive, ArrowRight, Cpu, Database, Mail, Server, ServerCog } from 'lucide-solid'; import type { ConnectionType, ProbeCandidate } from '@/api/connections'; import { AddressProbeStep } from './AddressProbeStep'; import { @@ -30,23 +30,23 @@ export interface ConnectionEditorProps { onSaved?: () => void; } -const DEFAULT_MANUAL_TYPES: ConnectionType[] = ['pve', 'pbs', 'pmg', 'vmware', 'truenas', 'agent']; +// Platform integrations — connect to a product's management API. Peers of +// each other. The agent is NOT in this list: it is a different kind of +// integration (see the dedicated section below the grid) and surfacing it +// as a tile alongside these hides what it actually adds. +const DEFAULT_PLATFORM_TYPES: ConnectionType[] = ['pve', 'pbs', 'pmg', 'vmware', 'truenas']; interface TileMeta { icon: Component<{ class?: string }>; description: string; } -const TILE_META: Partial> = { +const PLATFORM_TILE_META: Partial> = { pve: { icon: Server, description: 'VMs, containers, storage, backups' }, pbs: { icon: Archive, description: 'Backups, sync and verify jobs' }, pmg: { icon: Mail, description: 'Mail stats, queues, quarantine' }, vmware: { icon: ServerCog, description: 'vCenter or ESXi clusters' }, truenas: { icon: Database, description: 'Pools, datasets, replications' }, - agent: { - icon: Cpu, - description: 'Host metrics, or bare-metal Linux / Unraid / FreeBSD', - }, }; export const ConnectionEditor: Component = (props) => { @@ -60,7 +60,9 @@ export const ConnectionEditor: Component = (props) => { ); const [selectedCandidate, setSelectedCandidate] = createSignal(null); - const manualOptions = createMemo(() => props.manualTypeOptions ?? DEFAULT_MANUAL_TYPES); + const platformOptions = createMemo(() => + (props.manualTypeOptions ?? DEFAULT_PLATFORM_TYPES).filter((type) => type !== 'agent'), + ); const activeType = () => selectedType(); const showCredentialSlot = () => activeType() !== null; @@ -90,55 +92,95 @@ export const ConnectionEditor: Component = (props) => { +
chooseManualType('agent')} /> -
-
+
+
+
-
- - {(type) => { - const meta = TILE_META[type]; - const Icon = meta?.icon ?? Server; - const label = CONNECTION_TYPE_LABELS[type] ?? type; - const isAgent = type === 'agent'; - return ( -
- -
{meta!.description}
-
- - ); - }} - -
+ +
{meta!.description}
+
+ + ); + }} + + + + +
+
+
+ + +
} > diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index c79472ec9..bb279e8e3 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -75,8 +75,13 @@ describe('settings architecture guardrails', () => { it('keeps probe-first connection setup and inline node credentials on the shared editor model', () => { expect(connectionEditorSource).toContain("import { AddressProbeStep } from './AddressProbeStep';"); - expect(connectionEditorSource).toContain('const DEFAULT_MANUAL_TYPES: ConnectionType[] ='); + // Platform integrations render as peer tiles; the agent lives in its own + // section below so it can explain what host-level telemetry adds instead + // of being mistaken for one more peer of Proxmox / VMware / TrueNAS. + expect(connectionEditorSource).toContain('const DEFAULT_PLATFORM_TYPES: ConnectionType[] ='); + expect(connectionEditorSource).not.toContain("'agent'] ="); expect(connectionEditorSource).toContain('