From 38b3ec4a3a4d979160c1f82e4fcde7ec3832376f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 7 Jun 2026 22:18:30 +0100 Subject: [PATCH] Discovery panel: show a human-runnable CLI command, not Assistant plumbing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-guest Discovery panel dropped the raw cli_access string into a copyable code row — but cli_access is guidance written for the Assistant ('Use pulse_control with target_host ...'), which is meaningless to a person and reads as a fake command. Derive a concrete, human-runnable command from the resource coordinates (deriveCliCommand): 'pct exec 101 -- bash' for an LXC, 'docker exec bash' for Docker, and the layered 'pct exec 101 -- docker exec homeassistant bash' for a service in a nested container. Show that (copyable) with a 'run from the Proxmox/Docker host' hint. For types with no clean human command (VM, k8s, agent) fall back to the cli_access text, now clearly labelled as how the Assistant reaches it. The cli_access field itself (Assistant context) is unchanged. Unit-tested (deriveCliCommand); type-check + eslint clean. --- .../src/components/Discovery/DiscoveryTab.tsx | 35 +++++++++++++++--- .../components/Discovery/cliCommand.test.ts | 33 +++++++++++++++++ .../src/components/Discovery/cliCommand.ts | 37 +++++++++++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 frontend-modern/src/components/Discovery/cliCommand.test.ts create mode 100644 frontend-modern/src/components/Discovery/cliCommand.ts diff --git a/frontend-modern/src/components/Discovery/DiscoveryTab.tsx b/frontend-modern/src/components/Discovery/DiscoveryTab.tsx index 9a26f106d..24566653e 100644 --- a/frontend-modern/src/components/Discovery/DiscoveryTab.tsx +++ b/frontend-modern/src/components/Discovery/DiscoveryTab.tsx @@ -33,6 +33,7 @@ import { } from '@/utils/resourceAnalysisPresentation'; import { useDiscoveryTabState } from './useDiscoveryTabState'; import { orderFactsByActionability } from './factOrdering'; +import { deriveCliCommand } from './cliCommand'; interface DiscoveryTabProps { resourceType: ResourceType; @@ -151,6 +152,14 @@ export const DiscoveryTab: Component = (props) => { } return getConfidenceLevel(current.confidence); }); + // A concrete, human-runnable command for this workload (pct exec / docker + // exec, including the nested-container layer). null when there is no clean + // human command for the type, in which case we show the Assistant guidance. + const cliCommand = createMemo(() => { + const current = discovery(); + if (!current) return null; + return deriveCliCommand(props.resourceType, current.resource_id, current.cli_access); + }); const commandSettingsTarget = getDiscoveryCommandSettingsTarget(); const apiAccessSettingsTarget = getDiscoveryApiAccessSettingsTarget(); const showManualRunAction = () => props.showManualRunAction === true; @@ -824,12 +833,26 @@ export const DiscoveryTab: Component = (props) => { CLI Access - + + How the Pulse Assistant runs commands here: + {d().cli_access} +

+ } + > + +

+ Run from the{' '} + {props.resourceType === 'app-container' ? 'Docker host' : 'Proxmox host'}. +

+
diff --git a/frontend-modern/src/components/Discovery/cliCommand.test.ts b/frontend-modern/src/components/Discovery/cliCommand.test.ts new file mode 100644 index 000000000..93cbcc444 --- /dev/null +++ b/frontend-modern/src/components/Discovery/cliCommand.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from 'vitest'; +import { deriveCliCommand } from './cliCommand'; + +describe('deriveCliCommand', () => { + it('derives pct exec for a native LXC', () => { + expect( + deriveCliCommand('system-container', '101', 'Use pulse_control ... directly inside the container.'), + ).toBe('pct exec 101 -- bash'); + }); + + it('layers docker exec for a service in a nested container (HA-in-LXC)', () => { + const cliAccess = + 'Use pulse_control ... The service runs inside a Docker container named "homeassistant" ' + + '— prefix commands with: docker exec homeassistant .'; + expect(deriveCliCommand('system-container', '101', cliAccess)).toBe( + 'pct exec 101 -- docker exec homeassistant bash', + ); + }); + + it('uses docker exec for a Docker container', () => { + expect(deriveCliCommand('app-container', 'redis', undefined)).toBe('docker exec redis bash'); + }); + + it('returns null for types without a clean human command (VM, k8s, agent)', () => { + expect(deriveCliCommand('vm', '200', 'Use pulse_control ... inside the VM.')).toBeNull(); + expect(deriveCliCommand('pod', 'web-0', 'Use kubectl exec -n default web-0 -- ')).toBeNull(); + expect(deriveCliCommand('agent', 'node1', 'Use pulse_control ...')).toBeNull(); + }); + + it('returns null when the resource id is missing', () => { + expect(deriveCliCommand('system-container', '', 'x')).toBeNull(); + }); +}); diff --git a/frontend-modern/src/components/Discovery/cliCommand.ts b/frontend-modern/src/components/Discovery/cliCommand.ts new file mode 100644 index 000000000..cd67c8ad0 --- /dev/null +++ b/frontend-modern/src/components/Discovery/cliCommand.ts @@ -0,0 +1,37 @@ +import type { ResourceType } from '../../types/discovery'; + +/** + * Derives a concrete, human-runnable command for reaching a workload from its + * resource coordinates. + * + * The discovery `cli_access` field is guidance written for the Pulse Assistant + * ("Use pulse_control with target_host …") — it is not something a person types. + * This returns what a human would actually run (e.g. `pct exec 101 -- bash`), + * including the nested-container layer when the service runs in Docker inside an + * LXC/VM. Returns `null` when there is no clean human command for the type (the + * caller then falls back to showing the guidance text, e.g. k8s kubectl which is + * already human-readable, or VMs where SSH/credentials are the real path). + */ +export function deriveCliCommand( + resourceType: ResourceType, + resourceId: string, + cliAccess: string | undefined, +): string | null { + const id = (resourceId || '').trim(); + if (!id) return null; + + // A service running in a nested Docker container changes the access path; the + // backend records the container in cli_access as "docker exec …". + const nested = /docker exec (\S+)/.exec(cliAccess || '')?.[1]; + + switch (resourceType) { + case 'system-container': + return nested ? `pct exec ${id} -- docker exec ${nested} bash` : `pct exec ${id} -- bash`; + case 'app-container': + return `docker exec ${id} bash`; + default: + // vm (qm guest exec is non-interactive; SSH is the real path), pod (the + // guidance already shows a runnable kubectl exec), agent, etc. + return null; + } +}