Judge a linked agent's guest disks by that agent everywhere

PR 2669 made the guest drawer's filesystem rows follow a linked Pulse
agent's freshness when its filesystems fill in for a VM that has no
Proxmox guest filesystems. The workload table's disk cell and the
drawer History still judged the same reading only by the Proxmox read
reason. So one guest read as current in the drawer and as "Unavailable:
Guest agent not running" in the table and History, and a silent
agent's reading could not show as last known there.

getWorkloadGuestDiskRead now owns one rule for the table cell, the
drawer rows and History: current while the agent reports, last known
once it is stale, unavailable once the guest is stopped. Proxmox's own
guest filesystems keep their read reason.

The rule also covers the aggregate disk metric, because Proxmox records
no guest disk usage without guest filesystems, so the manual link merge
keeps the agent's metric. Three paths broke that:

- The poller built its previous VM from the merged metric, so the next
  poll carried the agent's disk forward as a Proxmox prev- read, and
  Proxmox priority then froze it over the agent's live value. Only
  Proxmox's own disk reading is carried now.
- agent.stale was omitempty, and the browser merges agent facets field
  by field, so a resumed agent stayed "stopped reporting" until a
  reload. It is always sent now.
- The same merge kept filesystems a native Proxmox VM facet or a native
  agent facet had withdrawn, so the rows did not switch source.
This commit is contained in:
rcourtman
2026-10-07 20:28:34 +01:00
parent 48c2d6cdce
commit 1231392e8d
23 changed files with 704 additions and 91 deletions
@@ -294,6 +294,14 @@ expose only a raw reason. A same-VM fresh observation withdraws the notice and
updates values without remounting the row. Permission, disabled-agent and actual
unavailable cases retain their own explanations.
When a linked Pulse agent's filesystems fill in for a VM with no Proxmox guest
filesystems (`disksFromAgent`), the reading follows that agent instead of the
Proxmox reason: current while it reports, last known once it is stale, and
unavailable once the guest is stopped. `getWorkloadGuestDiskRead` in
`workloadGuestPresentation.ts` owns that rule for the workload row's disk cell,
the guest drawer's filesystem rows and its History, so the three cannot
disagree about one guest; `workloadGuestPresentation.test.ts` pins it.
Verification: `workloadGuestPresentation.test.ts`, the mounted `GuestRow`,
`StackedDiskBar` and `GuestDrawerOverview.filesystems` regressions,
and `browser-tests/guest-disk-deferral.cjs` cover the fixed reasons, retained/no
@@ -4491,6 +4491,13 @@ disk usage when the last VM snapshot is still recent guest-agent truth rather
than an already carried-forward fallback. That keeps transient guest-agent or
status-call failures from regressing a VM back to misleading allocated-disk
data while still avoiding indefinite replay of stale disk summaries.
The previous VM that `previousVMFromView` builds from the unified read state
carries disk usage only when it is Proxmox's own reading. When a manually
linked Pulse agent's disk filled in because Proxmox had no guest filesystems
(`VMView.DiskFromLinkedAgent`), carrying it would relabel the agent's value as
a Proxmox `prev-` read, and with Proxmox outranking the agent on guests that
copy would then freeze over the agent's live disk.
`TestPreviousVMFromViewKeepsLinkedAgentDiskOutOfProxmoxCarry` pins both owners.
That compatibility boundary also applies to historical snapshot labels that may
still exist in tests, live in-memory state, or pre-canonical diagnostic paths:
legacy aliases such as `rrd-available`, `rrd-data`, `node-status-available`,
@@ -2712,9 +2712,26 @@ unavailable once the guest is stopped, whether its status or the Proxmox
`vm-stopped` reason says so, and Proxmox's own guest filesystems keep their
Proxmox read state. The two flags ride the existing row mapping and
add no query or poll; `GuestDrawer.test.tsx` and `useWorkloads.test.ts` pin
both sources. The workload table's disk cell and the drawer History still read
the aggregate disk metric and the Proxmox reason, and the agent's OS, addresses
and physical disk health carry no stale cue yet.
both sources. The workload table's disk cell (`useGuestRowState.ts`,
`GuestRow.tsx`) and the drawer History (`guestDrawerModel.ts`) judge the same
guest through the drawer rows' rule, `getWorkloadGuestDiskRead` in
`workloadGuestPresentation.ts`: a silent agent's bars carry the Last known
label, a live agent's bars stay current under Proxmox `agent-not-running`, and
History reads the aggregate disk metric as current, last known or unavailable
the same way. The rule covers the aggregate because Proxmox records no guest
disk usage without guest filesystems, so a manually linked guest then keeps the
agent's disk metric, and the poller no longer carries that agent reading
forward as Proxmox's own `prev-` read (see the monitoring contract).
`TestResourceRegistry_ManualGuestAgentLinkDiskMetricFollowsProxmoxGuestFilesystems`
pins that merge; `GuestRow.test.tsx` and `GuestDrawer.test.tsx` pin the cell and
History. On the websocket path (`resourceStateAdapters.ts`) a native Proxmox VM
facet's new guest-read outcome drops filesystems it no longer reports, a native
agent facet without `disks` drops the agent's, and the agent facet always
carries `stale`, so the rows switch source and lose the Last known label when
the agent resumes, without a reload. A stale Proxmox source
can still lose the aggregate to a live agent while its own filesystem rows
remain; such a guest stays on the Proxmox rule. The agent's OS, addresses and
physical disk health carry no stale cue yet.
Guest, node, and Docker-host drawer headers follow the same frontend-primitives dependency
boundary for collapse: Workloads owns which inline row is selected and the
close handler, while
@@ -6867,6 +6867,15 @@ The performance report and reporting runtime snapshot handlers apply the same
test before tabulating a disk temperature. That changes only which held value a
report shows, not any storage or recovery path.
### A linked agent's stale flag and guest disk owner open no storage path
`internal/unifiedresources/types.go` now always sends `AgentData.Stale`, so a
resumed agent clears its stopped-reporting mark in browsers that merge agent
facets field by field, and `VMView.DiskFromLinkedAgent` keeps the poller from
carrying a linked agent's guest disk forward as a Proxmox read. Both describe
which source's reading is current; no backup, retention, migration or recovery
path is added or moved.
### Demo write guard ignores websocket upgrade headers
`internal/api/demo_middleware.go` no longer exempts every request carrying
@@ -2303,6 +2303,16 @@ evidence of what the array was doing when the agent went quiet, but without
live status colour. Where a retained rebuild percentage is shown, it reads
"Rebuild was at N%" with no speed or progress bar, and a member that was not
healthy names its state in its badge.
A Pulse agent manually linked into a guest carries the same signal there.
`AgentData.Stale` is always on the wire, because browsers merge agent facets
field by field (`resourceStateAdapters.ts`) and would keep a resumed agent's
omitted false as true; `TestAgentDataAlwaysSendsStale` pins it. That always
present `stale` also marks a native agent facet, so one without `disks` has
withdrawn them, and a native Proxmox VM facet's new guest-read outcome likewise
drops `disks` it no longer reports; either way the other source's filesystems
take over without a reload (`resourceStateAdapters.test.ts`). `VMView.DiskFromLinkedAgent` names the agent
as the owner of a guest's selected disk metric, which the monitoring poller
reads before it carries a Proxmox disk reading forward.
For SMART disk
temperatures on rows that still render, the provenance travels on
`agent.sensors.smart[].collection`: the Machines temperature cell