Commit Graph

6328 Commits

Author SHA1 Message Date
rcourtman fc72e3d8d1 Fix LXC Docker container flicker from partial inventory polls
The Proxmox-side LXC Docker inventory script masked `docker ps`
failures with `|| true`, so when `pct exec` was truncated, slow, or
`docker ps` itself transiently failed, the parser produced an
ok=true report with `containers=[]` and `ApplyDockerReport` blanked
the host's container list. Containers then reappeared on the next
successful poll, producing the visible flicker on the Docker page.

Emit a `PS_OK` marker only when `docker ps` exits cleanly, and
refuse to apply the report when the marker is absent. A genuine
"host has zero containers" state still applies (PS_OK present,
zero CONTAINER lines), but a truncated or failed poll now skips
apply and lets the previous container list survive the inter-poll
gap. Add regression tests for both paths.
2026-05-19 10:00:26 +01:00
rcourtman 1f66a2f93f Collapse Docker page to single unified surface
The "Hosts" tab already rendered the hosts table, the containers
WorkloadsSurface, and the Swarm services table together, so the
separate Containers and Swarm services sub-tabs were strict subset
views of overview. Other platform pages (Proxmox, TrueNAS,
Kubernetes) use sub-tabs for genuinely different data per tab; the
Docker shape was the odd one out.

Drop PlatformSectionTabs from DockerPageSurface and render the
unified hosts + containers + services stack unconditionally. Remove
the now-dead DOCKER_TAB_SPECS / DockerPageTabId / DockerTabSpec /
buildVisibleDockerTabSpecs exports from dockerPageModel. The
DockerPage no longer interprets the URL segment, so legacy
/docker/overview, /docker/containers, /docker/services bookmarks
continue to resolve via the existing /docker/* wildcard route in
App.tsx.
2026-05-19 09:52:10 +01:00
rcourtman 90482607da Bind Proxmox install command tokens on first use 2026-05-19 09:47:05 +01:00
rcourtman 004d0032ee Fix Disconnected badge label overflow
The disconnected branch of ConnectionStatusBadge used the connected
state's min-w-6 h-6 group-hover:px-3 sizing, designed for a circle dot
that expands on hover. But showLabelByDefault() is true for any
non-healthy tone, so the label was rendered without horizontal padding
and spilled against the pill's right edge. Switch to py-1 px-2 to match
the warning and reconnecting branches, which already pad for an
always-visible label.
2026-05-19 09:31:38 +01:00
rcourtman 391cea1b13 Fix managed hot-dev LAN browser binding 2026-05-19 09:08:32 +01:00
rcourtman 0c1cdf7488 Add opt-in Proxmox LXC Docker inventory 2026-05-18 23:44:24 +01:00
rcourtman f0ad7f1284 Fix TrueNAS fractional uptime decoding
Refs #1469
2026-05-18 23:12:27 +01:00
rcourtman 91d7102a27 Make managed hot-dev verification deterministic 2026-05-18 17:29:50 +01:00
rcourtman 6feb85245f Stabilize first-session settings handoffs 2026-05-18 17:27:03 +01:00
rcourtman 84f627e22a Move Proxmox host drawers to the host table 2026-05-18 17:19:32 +01:00
rcourtman 5a405f7f07 Protect inside-guest Docker visibility boundaries 2026-05-18 14:16:15 +01:00
rcourtman dca06d6a55 Clarify Docker update check failures 2026-05-18 11:36:56 +01:00
rcourtman 8511016bd8 Share per-disk drawer card between Docker and node drawers
Extracts the per-disk Storage card introduced for the Docker host drawer
into a shared `DrawerDiskListCard` under `components/Workloads/` and
threads an optional `disks` prop through `NodeDrawer` →
`NodeDrawerOverview`. The Docker drawer now imports the shared component
instead of carrying its own copy, and `NodeDrawerOverview` renders the
shared card whenever the caller supplies two or more disks (falling back
to the existing aggregate Storage rows otherwise).

`ProxmoxNodesTable` is left untouched in this commit because the file is
currently dirty under another agent's lane; wiring the `disks` prop on
its `<NodeDrawer>` call site is a separate one-line follow-up once that
lane settles.
2026-05-18 11:30:54 +01:00
rcourtman 8302229a26 Refine workload sparkline I/O density 2026-05-18 11:29:15 +01:00
rcourtman 08e855483f Show Docker container update status in rows 2026-05-18 11:28:00 +01:00
rcourtman e3cce1efba Show per-disk breakdown in Docker host drawer Storage card
The previous Storage card surfaced only the aggregated host.disk values,
which on multi-drive hosts (Unraid in particular) is just the rootfs and
misses the data drives entirely. Drawer drill-down should be more
informative than the row's hover tooltip, not less.

When the host's agent reports multiple disks, the Storage card now spans
two columns and lists each disk with mountpoint, percent, used/total, and
a threshold-coloured bar that matches the StackedDiskBar tooltip palette.
Single-disk hosts keep the existing aggregate Usage/Total/Free rows.
2026-05-18 11:21:53 +01:00
rcourtman 1ebcf37525 Make Docker I/O columns telemetry-aware 2026-05-18 11:08:00 +01:00
rcourtman 0ebe2ab8c7 Tighten Docker host drawer overview field access
Replace inline `as { current?: number }` / `as { usage?: number }` casts on
the memory union with a `getNumericField` helper, and drop the stray
`agent()?.connectionState` lookup (that field lives on vSphere metadata,
not the agent telemetry record) so the Connection row reads the host
status directly.
2026-05-18 11:04:46 +01:00
rcourtman 2c5b367048 Add Docker host detail drawer mirroring Proxmox node pattern
Clicking a Docker host row in the platform-page hosts table now expands an
inline drawer with Overview (System / Runtime / Containers / Memory /
Storage / Telemetry, plus Swarm context when present) and History tabs.
The History tab reuses GuestDrawerHistory against the host's agent
telemetry, so utilisation, network I/O, disk I/O, and thermals share the
same backend and rendering primitives as the Proxmox NodeDrawer.

Drawer placement (inline TableRow with colspan) and keyboard activation
mirror ProxmoxNodesTable so users get the same drill-down affordance on
both platform pages.
2026-05-18 11:02:51 +01:00
rcourtman 5fb583d26f Use Docker-native container workload columns 2026-05-18 10:50:34 +01:00
rcourtman 68431aaaf1 Hide Docker container disk column by default 2026-05-18 10:38:19 +01:00
rcourtman 77cfd6aa4a Fix typecheck of identified-service test fixtures
Pre-push tsc rejected the discovery test fixtures because ResourceDiscovery
has more required fields than the minimum needed to exercise the new
identified-service helper. Widen the vi.fn return type so
mockResolvedValueOnce accepts partial-record fixtures, and cast the
fixtures through `unknown as ResourceDiscovery` (matching the existing
pattern used for the ports/facts subobjects in the same suites).
2026-05-17 23:24:53 +01:00
rcourtman fe7b618e8f Surface identified service in GuestDrawer overview
Discovery records carry rich identification (service name, category,
ports, cli access, paths) but until now they only rendered inside the
Discovery sub-tab. Operators opening a workload drawer to look at memory
or backups had no signal that Pulse already knows the LXC is "Homepage
Dashboard" or the Docker container is "Proxmox Backup Server" — they
had to switch tabs to see it.

Add an "Identified Service" card to GuestDrawerOverview that shows the
service name, category, port count, CLI access hint, and confidence
percent. The card mirrors the existing System/Guest Info/Backup card
shapes so it slots into the same flex grid.

useGuestDrawerState passively loads the discovery record via getDiscovery
(no scan triggered — manual scans, progress UI, and approval prompts
stay owned by DiscoveryTab and useDiscoveryTabState). A new presentation
helper, getDiscoveryIdentifiedSummary, packages the record into the
display shape and returns null when the record has no meaningful
identification — same gate as the Discovery tab's hasValidDiscovery, so
both surfaces render or hide together.

Contracts: amend frontend-primitives Completion Obligations to fence the
new identified-service reducer to discoveryPresentation.ts, and amend
performance-and-scalability Current State to require that out-of-tab
discovery surfacing stay passive (no scans). Verification via
discoveryPresentation.test.ts and GuestDrawer.test.tsx covers the
populated/empty/null branches and the drawer card render.

Workloads table row surfacing is the natural next step but blocked
behind the parallel agent's in-progress WorkloadPanel/WorkloadsSurface/
useWorkloadsState changes; revisit after those land.
2026-05-17 23:21:11 +01:00
rcourtman 06fd4fc89e Bypass approval gate for trusted internal Discovery commands
Discovery wraps every probe in `docker exec <container> sh -c '...'`.
The agentexec command policy lists `^docker\s+exec\s` as RequireApproval
(a sound default for user-driven docker exec) and Discovery has no path
to mint or supply an ApprovalID. Result: every probe was rejected, the
scanner returned empty CommandOutputs, and the AI fell back to
"Unknown Infrastructure Resource" at confidence 0. The Discovery sub-tab
rendered empty after a "successful" run.

Add a Trusted bool to ExecuteCommandPayload on both the server-facing
agentexec type and the agent's wire struct. When set, the approval gate
is skipped on both ends and the server does not attempt to auto-mint an
approval grant (which would fail with "approval id is required").
PolicyBlock still applies; this is not a way to run arbitrary commands.

Only the discoveryCommandAdapter sets Trusted=true. The flag is never
populated from a deserialised HTTP body or any user-driven path. Patrol
fixes, Assistant remediation, and AI tool calls continue to flow through
the governed approval-record path with a real ApprovalID.

Contracts: amend agent-lifecycle Completion Obligations and Current
State to document the lone exception to the on-agent approval rail, and
amend ai-runtime to fence the Trusted flag to the discovery adapter
only.
2026-05-17 21:59:39 +01:00
rcourtman 7fac636bd5 Include containerId in Discovery resolver Pick type
Follow-up to the previous commit: resolveDiscoveryTargetForWorkload now
reads guest.containerId, so the Pick<WorkloadGuest, ...> input type must
include it or tsc rejects the property access.
2026-05-17 21:02:33 +01:00
rcourtman 25ae258bf4 Target Docker Discovery by container id, not workload hash
For app-container workloads, resolveDiscoveryTargetForWorkload was
populating the discovery resourceId with guest.id — the frontend's
synthetic workload hash (e.g. "app-container-ed79032e5852577f"). The
backend wraps that as docker exec <id> sh -c '...', the Docker daemon
has no container by that name, so every scan came back with empty
stdout and the AI returned "Unknown Infrastructure Resource" at
confidence 0. To the user the Discovery sub-tab looked empty after a
"successful" run.

guest.containerId is already populated from resource.docker.containerId
for Docker-managed app-containers (useWorkloads.ts:450). Use it as the
discovery resourceId so the agent can actually docker exec into the
right container. VM and system-container branches were already correct
because they use vmid for pct exec / qm guest exec.

Fix the existing workloadTopology test that locked in the broken
behavior, and add containerId to the workloads.test.ts fixture that
exercises the same resolver branch.
2026-05-17 20:59:05 +01:00
rcourtman d20671a736 Expose Discovery model override in AI settings
The backend has had DiscoveryModel + GetDiscoveryModel() with the
shared-default fallback alongside ChatModel and PatrolModel, but the
settings UI only surfaced Assistant and Patrol overrides. Discovery
silently used whatever the shared default was set to, with no way to
dial it independently — yet Discovery is the surface where a cheaper
model (e.g. Haiku) is most often the right tradeoff.

Thread discoveryModel through the form state, load, save payload, and
failure analyzer, and render a third picker in the Model Overrides
section mirroring the Assistant/Patrol pattern. Generalize the section
header and shared-default copy so they no longer enumerate surfaces.

Extend the frontend-primitives contract to recognize Discovery alongside
Assistant and Patrol as a per-surface override, and lock the picker
presence into settingsArchitecture.test.ts.
2026-05-17 20:39:46 +01:00
rcourtman b7ce2b6cc0 Map Discovery app-container to docker at API boundary
Frontend canonicalizes Docker containers as app-container, but the
Discovery API only accepts docker. Without translation, every Discovery
trigger for a Docker container 400'd at type validation before any agent
dispatch — surfaced to users as "unsupported resource type 'app-container'".

Mirror the existing pod -> k8s translation in toDiscoveryAPIResourceType.
The agent transport layer already handles ResourceTypeDocker via "docker
exec" on the connected agent host; only the URL-path resource type needed
the alias.
2026-05-17 20:35:06 +01:00
rcourtman 0c9ea7504e Normalize inactive Docker Swarm evidence 2026-05-17 18:45:24 +01:00
rcourtman 29b8d5161d Refine Docker platform runtime presentation 2026-05-17 18:41:22 +01:00
rcourtman dc0635606d Keep Pulse runtime boundaries model-owned 2026-05-17 18:24:13 +01:00
rcourtman 251e8844dc Gate platform navigation by resource evidence 2026-05-17 18:16:15 +01:00
rcourtman 2cb94d66f1 Capture platform page overlap decision 2026-05-17 18:14:26 +01:00
rcourtman 5e789f9a1d Align AI docs with model-owned reasoning 2026-05-17 17:59:22 +01:00
rcourtman 2588ed8baa Fix Docker host overview metrics 2026-05-17 17:57:27 +01:00
rcourtman 0b08967bc5 Align AI docs with model-owned reasoning 2026-05-17 17:54:56 +01:00
rcourtman a31df2c2c2 Restore compact v5-style filter rails 2026-05-17 17:51:37 +01:00
rcourtman ee93190f4b Polish Proxmox host disk cell: narrower column + per-bar hover
The disk column was sized for the old "[bar] N% (used/total)" content
(~150-180px). With vertical micro-bars it only needs ~80-110px, so
the disk weight drops from 15 to 7 on desktop/compact (and the
proportional weights drop on mobile/tablet). Freed space redistributes
to Node/CPU/Memory/Cluster which all have more content to show.

Each vertical bar also gets a cursor-help cursor, a subtle ring on
hover, and a brightness/scale tick so users discover that bars are
individually inspectable — the native browser title tooltip on each
bar (disk name + percent + sizes) was already wired; this just makes
it visually discoverable.
2026-05-17 17:06:18 +01:00
rcourtman 6bdc10d74f Retire dead infrastructure connections table 2026-05-17 15:54:49 +01:00
rcourtman fcc7626a9a Render multi-disk Proxmox hosts as vertical micro-bars
Adds a 'vertical-bars' mode to StackedDiskBar and opts the hosts
table into it when a host has more than one disk. Each disk renders
as one 5px-wide vertical bar (fill height = utilization, color =
threshold), grouped and centred in the cell. All disks get identical
visual weight, no single disk is named or privileged, and 1-10 disks
all render the same way. Hover keeps the full per-disk breakdown.

Single-disk hosts still go through the original single-bar path.
2026-05-17 15:43:31 +01:00
rcourtman cb31caa1b3 Revert Proxmox host disk sub-rows
The sub-row layout under each multi-disk host left the leading
columns (node/version/uptime/cpu/memory) blank on every disk row,
which fragmented the table visually and made the page feel empty
even when the disk content itself was readable. Going back to
single rows; the right fix needs more thought.

This reverts c0896fbc9 and 95758b6ef.
2026-05-17 15:28:57 +01:00
rcourtman c0896fbc94 Constrain Proxmox disk sub-rows to a tighter column span
Previous version spanned from Disk all the way to Cluster, which gave
the per-disk bar far more room than it needs and visually fragmented
the table grid. Cap the colspan to Disk plus two right neighbours so
the sub-row sits in a sensible ~400px region, and fix the bar at w-20
instead of letting it stretch. Cts/Cluster columns now stay aligned
with the host row above.
2026-05-17 15:23:57 +01:00
rcourtman 95758b6efd Show host disks as sub-rows under the Proxmox hosts table
The disk column was cramming N inline lanes into a ~197x16 cell, which
fell apart visually past three disks and forced label/percent collisions.
Hosts table is small (a handful of rows), so use the vertical space:
each multi-disk host gets one sub-row per disk beneath it, with the
sub-row colspan'd from the disk column through cluster so name, bar,
percent, and used/total all fit naturally. The host row's disk cell
collapses to a neutral "N disks" pill with a worst-threshold dot, so
nothing is hidden but no single disk is singled out by name.
2026-05-17 15:20:12 +01:00
rcourtman da666e452d Move card-layout problem line below the status footer
The narrow-viewport card was rendering the row's status badge, problem
line, and Manage button as siblings inside a single flex-justify-between
row in the card footer. A long problem string ("Agent offline · 4h
ago", "Credentials invalid · 14 days") would squeeze the layout
unpredictably and push the Manage button into wrap territory.

Pull the problem line out of the flex row and onto its own line
beneath the status/Manage row, matching the wide-table treatment.
2026-05-17 15:10:35 +01:00
rcourtman 555c9902a9 Collapse Connected systems table from 6 columns to 4
The SOURCE and ENDPOINT columns each carried a single static fact per
row (which collection method, which URL) that didn't help the user
scan for problems and that pushed the three columns the user actually
needed (System, Coverage, Status) into narrower lanes.

Fold source and endpoint into the System cell as a secondary line
beneath the name and subtitle: a small source badge inline with the
truncated endpoint URL. The cluster row, which has no endpoint, just
shows the source badge. Member rows follow the same pattern under
their indent.

New widths (System / Coverage / Status / Actions): 36 / 32 / 24 /
7rem at standard width, 34 / 34 / 22 / 7rem at xl. Section group
headers and lastError/empty-state rows colspan from 6/5 to 4/3.

Card layout (narrow viewport) is unchanged — it already stacks fields
vertically, not as table columns.
2026-05-17 14:59:21 +01:00
rcourtman 0fbb8b7124 Render compact per-disk usage lanes 2026-05-17 14:59:03 +01:00
rcourtman a48a1acd8d Compute attached-agent problem directly, drop synthesized signal
The agent-offline detail line was being produced by synthesizing a fake
FleetGovernanceSignal with key 'agent-attachment' and laundering it
through visibleFleetGovernanceSignals so the chip filter would pick it
up. With the chip stack gone and the row exposing a single
InfrastructureRowProblem, the synthesis is dead weight — it pretended
to be server-emitted governance state when it was really derived from
the agent's connection state.

- Replace agentAttachmentSignal with agentAttachmentProblem, returning
  InfrastructureRowProblem directly. The row builder applies it ahead
  of the fleet-highlights problem, so an offline agent always wins over
  a downstream credential/config drift signal on the same row.
- Remove 'agent-attachment' from FleetGovernanceSignalKey now that no
  signal carries it.
- Simplify the setup-summary Live/Needs attention counters to read off
  the user-visible row state (statusLabel, problem) instead of
  re-deriving via signal predicates. The previous path could
  in principle disagree with what the row actually rendered.
- Drop rowFleetSignals, rowHasFleetTone, rowHasVisibleFleetTone — all
  now unused.
2026-05-17 14:48:44 +01:00
rcourtman 41349be7d8 Fix multi-disk pressure bars 2026-05-17 14:39:57 +01:00
rcourtman 4cbae25d34 Make TableCardHeader title-only render nothing
Each platform overview page (Docker, Kubernetes, TrueNAS, vSphere)
composed its own <TableCardHeader title="Hosts" /> etc. independently,
so dropping the redundant title strips on Proxmox in the previous
commit didn't propagate. The architecturally correct fix is at the
primitive: the header strip only earns the chrome when it has real
content to show.

- TableCardHeader now renders null unless there's an `actions` slot or
  an active `showClearAction`. Title-only is a silent no-op — every
  platform-table caller stops showing a vestigial label automatically.
- `title` becomes optional on the props type so callers that intend to
  use the strip purely for an action button (no caption) don't need to
  pass a dummy.
- Title still renders when paired with actions, which keeps
  StorageContentCard's "STORAGE / Storage|Physical Disks" header and
  RecoveryProtectedInventorySection's "Protection coverage / Recovery
  events" back-button strip working as before.

Net effect: Docker, Kubernetes, TrueNAS, vSphere overviews now match
Proxmox without touching any per-platform file, and future platform
tables can't accidentally re-introduce the strip by typing
title="Hosts".
2026-05-17 14:33:41 +01:00
rcourtman bcc13255d5 Narrow primaryRowProblem tone return type at the helper
TypeScript can't narrow FleetGovernanceSignal.tone through the find
predicates, so the helper now returns the literal tone for each branch
instead of forwarding the wider signal tone.
2026-05-17 14:30:10 +01:00