Commit Graph

7245 Commits

Author SHA1 Message Date
rcourtman e63da80b65 Colour Alert History frequency bars by severity
The frequency chart drew every period in one blue under a legend of
coloured severity dots that described nothing in the bars, left info
alerts out of that legend, and used a floored log scale on which a
25-alert burst looked barely three times one alert.

Each period now stacks critical, warning and info in the legend's own
colours, counted with the alerts contract's closed severity vocabulary,
and bars scale by the square root of their share of the busiest period so
a burst stands clear while quiet periods keep a readable mix. The legend
counts all three severities, and the tooltip and accessible name carry
each period's breakdown. Light-theme warnings use amber-700 because
yellow on the chart surface was under 2:1, and a selected period is
ringed instead of dimming the rest below 3:1.

The chart's two test files are registered as alerts frontend proofs.
2026-10-14 21:34:03 +01:00
rcourtman 477a81e603 Name Swarm rollouts truly and colour the cells behind each dot (#2952)
A Swarm service's dot read "Rollback paused" for Docker's
rollback_started and paused update states alike, contradicting the
Update column ("Rollback started"), and ignored rollback_paused. The
dot now says "Rolling back", "Update paused" or "Rollback paused", and
no replicas running outranks a rollout, since that service is down.

The cell behind each Swarm dot's verdict reads in its tone through the
shared dockerStateTextToneClass helper: a service's Running count (and
"/desired" on phones, which hide Desired) and its Update words when a
rollout needs a look; a task's Current state, now the dot's own word
with Docker's token and error on hover, and the error in the expansion
for a phone that cannot hover; a node's Reachability for an unreachable
manager and Availability for a drained or paused node. Desired reads
as words beside Current.

The task fixture's "running 2 minutes" state was never sent by the
agent (it reports task.Status.State); it now uses "running".

Reviewed by Sol from desktop and phone screenshots.
2026-10-14 21:15:05 +01:00
rcourtman f58f678398 Colour Kubernetes status words by their dot, from one platform helper
A Pod's failure reason ("CrashLoopBackOff") sat in plain text beside a
red dot, cut to "CrashLoopBa…" at 1440px, and a phone, which hides the
Status column, showed only the dot. Deployments and controllers had the
same gap: a short Ready count and "1 not ready" read plain beside an
amber dot.

The tones the Docker tables use now live in one platform helper,
getPlatformStatusTextToneClass; dockerStateTextToneClass delegates to it
and PlatformIssueReason takes its colours from it. The Kubernetes tables
colour the cell behind each dot's verdict: a Pod's Status words, a
Deployment's Ready count while replicas are short, a controller's
Exceptions. The Pods Status column widens from 8% to 12% (from Node,
Restarts and Image) so CrashLoopBackOff reads whole, and a phone reads
the same words under the Pod name.

A new browser fixture, kubernetes-status-rows, covers the three tables.
Reviewed by Sol from desktop and phone screenshots.
2026-10-14 20:46:35 +01:00
rcourtman eb42ada79a Open a Proxmox storage alert's pool on the Storage page
A full or degraded pool's alert linked to the top of the Proxmox overview,
so the operator had to find the pool themselves. The alert card now links
Proxmox storage alerts to /proxmox/storage?resource=, and Storage opens and
reveals that pool.

ZFS pool and device alerts are raised under composite ids the resource
registry cannot open, so checkZFSPoolHealth stamps the owning storage as
storageResourceId metadata and the card prefers it. An unstamped composite
id opens Storage without naming a pool.

The Storage deep-link hook matched only Ceph pools by id or any name, so
alert source ids and shared names (every node has a local-zfs) missed or
opened the wrong pool. It now matches id, then metrics id, then a unique
name, resolves anything else through the registry, and opens every matched
pool. It acts only while Storage is the active Proxmox tab, because the
mounted-but-hidden Storage tab otherwise read guest links, and a pool the
operator opens or closes during a lookup outranks the link.
2026-10-14 20:05:26 +01:00
rcourtman 3a7304d626 Name Swarm rollouts truly and colour the cells behind each dot
A Swarm service's dot read "Rollback paused" for Docker's
rollback_started and paused update states alike, contradicting the
Update column ("Rollback started"), and ignored rollback_paused. The
dot now says "Rolling back", "Update paused" or "Rollback paused", and
no replicas running outranks a rollout, since that service is down.

The cell behind each Swarm dot's verdict reads in its tone through the
shared dockerStateTextToneClass helper: a service's Running count (and
"/desired" on phones, which hide Desired) and its Update words when a
rollout needs a look; a task's Current state, now the dot's own word
with Docker's token and error on hover, and the error in the expansion
for a phone that cannot hover; a node's Reachability for an unreachable
manager and Availability for a drained or paused node. Desired reads
as words beside Current.

The task fixture's "running 2 minutes" state was never sent by the
agent (it reports task.Status.State); it now uses "running".

Reviewed by Sol from desktop and phone screenshots.
2026-10-14 19:56:40 +01:00
rcourtman 02d21333c3 Merge remote-tracking branch 'origin/main' into fix/infrastructure-system-links 2026-10-14 19:30:26 +01:00
rcourtman 3bf85f166a Move the connected-systems note clear of main's runtime note
Both branches appended a dated section at the end of agent-lifecycle.md, so
merging main conflicted. Taking main's file with this branch's note one
section earlier keeps both notes and lets main merge cleanly.
2026-10-14 19:25:05 +01:00
rcourtman 474efcee86 Link connected systems to the page that shows their data
Settings > Infrastructure lists each connection with a health state and a
Manage button, but nothing pointed to where its data appears. A user saw
their PBS as Active there and could not find it anywhere in monitoring,
because PBS servers live under Proxmox > Backups (#2905).

A connected API system's name now links to its page: Proxmox VE to the
Proxmox overview, PBS to Proxmox > Backups, Mail Gateway to Proxmox >
Mail Gateway, vCenter, TrueNAS, Docker and Kubernetes to theirs. Agent
hosts and availability checks get no link, because an agent appears on
different pages depending on what it runs. The link is underlined at
rest so touch users can see it, works in read-only mode, and leaves the
width-budgeted Actions column unchanged.
2026-10-14 18:23:51 +01:00
rcourtman 72bfeee246 Let a paid community install switch to the Pro runtime
A paid key activated on the public community build unlocks only the
licence-gated features compiled into that build. Patrol modes, container
update risk, Audit, RBAC and Reporting need the private Pulse Pro
runtime, and the only way to get it was the download portal. Licence
data from 14 October shows 6 of 36 v6 buyers and 39 of 109 migrated v5
licences still running only the community build.

GET /api/updates/pro-runtime reports whether this install can switch,
and POST /api/updates/pro-runtime/apply performs it. The license server
download broker decides eligibility from the activation's licence, the
same broker the Pro binary already updates from, so a refusal there
reads as not_eligible and any other failure as unavailable. Without an
activation the check answers not_activated without a network call.

The in-place switch is limited to systemd and Proxmox LXC installs. It
runs through ApplyUpdate with an explicit SwitchToProRuntime flag, so
ordinary community updates never take the broker path. It accepts the
Pro build of the running version or newer, refuses an older pin, and
keeps the signed-archive, hash, self-test, backup and history-rollback
steps of a normal update. Docker installs get the broker's
digest-pinned image commands instead. The Settings card that uses these
routes follows separately.
2026-10-14 18:20:02 +01:00
rcourtman 90ec416d2f Colour a network's attached-container state words by their dot
Inside a Docker network's expansion, each attached-container card showed
its state words ("Unhealthy", "12 restarts") in plain text beside a red
or amber dot. They now use the shared dockerStateTextToneClass helper,
the same tones as the containers table's State words: red for a
problem, amber for a warning, muted for stopped, plain for running.

Reviewed by Sol from desktop and phone screenshots.
2026-10-14 17:54:00 +01:00
rcourtman bb627673c1 Give a Docker network row one dot for what is on it
A network row showed two dots: a green one on its name, from the network
resource's own status (always online unless its host is lost), and a red
one beside "1 needs attention". The health chips filtered on the green
one, so that network counted as Healthy.

The row now has one dot, in the name cell: the network's own status when
that is worse, otherwise amber while anything attached needs attention,
otherwise green. Amber because the network itself is up; each attached
container keeps its own red or amber dot in the expansion. The State
words take that tone without a second dot, and the Healthy / Degraded /
Offline chips filter by the same verdict.

Docker State words on both tables now come from one tone helper,
dockerStateTextToneClass, so the containers table looks the same.

Reviewed by Sol from desktop screenshots.
2026-10-14 17:20:17 +01:00
rcourtman ac18c1d958 Filter a network's running containers by Docker's run state
A Docker network's attached-containers Running filter matched only
rows with a green dot, so a container that was running but unhealthy
or restarting too often dropped out of "Running" entirely.

Attention stays the rows' verdict; Running and Other now follow
Docker's run state, so such a container matches Running and Attention
both. The sections stay one per row, Attention first, so the Running
view lists it under its Attention heading. Other reads "not known to
be running", which covers a container with no reported state.

The network's State counted only red rows ("1 needs attention" beside
an Attention chip of 3); it now counts every Attention row, the dot
taking the worst. On phones the Subnets cell and the Driver header were
hidden, so "bridge" sat under SUBNETS; the compact layout now drops
both columns together, leaving them to Network details and giving
State its room. The attached-containers header shows the total, and
the filtered count reads once, in the toolbar.

The browser fixture gains a Networks view over the existing containers.
Reviewed by Sol from desktop and phone screenshots.
2026-10-14 17:04:07 +01:00
rcourtman 4c82d303cd Colour a crash-looping container's dot by its restart verdict
A running container Docker had restarted more than five times read four
ways: a green row and drawer dot, amber "N restarts" words on phones, a
red count in the wide Restarts column and a red lifted drawer row. The
attention-first sort ranked it with healthy containers.

mapDockerContainerStatus now owns one restart verdict: running past five
restarts is warning, "N restarts". The row dot, its title, the drawer
header dot and the sort read it in every layout. Where a row has a
Restarts column the count reads amber there and State keeps Docker's own
word; on phones State says "N restarts". The drawer's Restarts row is
amber, and its observed state stays Docker's word so the restart is
lifted once.

Amber rather than red because the container is running. Docker's count
is a total rather than a rate, which the hovers now say; the alert
engine still owns the windowed restart-loop alert.

Reviewed by Sol from desktop and phone screenshots.
2026-10-14 16:30:07 +01:00
rcourtman 3c9d36172a Read a container's state under its name on narrow phones
Below a 360px table (most phones at 360-393px) the shared shell gives
the name column a 40% floor, and the State column beside it, both
metric pills and the Update control cut "Unhealthy" to "Unhe…" and
"Exited (139)" to "Exite…". Narrowing the name from the colgroup does
not help: the browser takes the larger of the header and column widths
and rescales everything else.

The narrow layout drops the State column and shows the same words, tone
and title on a second line under the container name, on every row so
rows keep one height. CPU, memory and Update share the rest, which
keeps a 100% pill and the Update label whole at 320px too. The phone
layout and up keep their State column.

A saved sort now applies only while its column's header shows, so a
State sort stops reordering phone rows with no visible indicator; this
replaces the grouped view's Host/Engine special case. The name cell
only adds its column wrapper when a table passes a second line.

Reviewed by Sol from before/after screenshots at 320, 390 and 430px.
2026-10-14 16:03:01 +01:00
rcourtman 861d0a94b3 Colour a container with no reported state by its unified status
The container status mapper reads Docker's own container state. When a
container reported none, the unified status stood in, but only "online"
and the stopped states were understood, so a degraded or offline
container fell through to a grey "Unknown"-style dot in its row, its
State words and its drawer header, while every other resource reads
offline as red and degraded as amber.

With no reported state (a blank one counts as none), lost-contact
statuses now read danger and degraded, warning, partial and recovering
read warning; unknown stays muted, and a reported state such as exited
still wins. Row dot, State words and drawer header share the mapper, so
all three move together.

Reviewed by Sol from desktop and phone screenshots; its blank-state
normalisation point is fixed here.
2026-10-14 15:29:40 +01:00
rcourtman e787ca857d Offer the update itself beside an available update in Worth checking
A container drawer lifted "Image update: Available" into Worth checking
but offered no next step; the update control lived only in the table's
Updates column, which a phone row hides.

The lifted row now carries the same shared Update button as the table,
both built from one helper (getDockerContainerUpdateAction), so
readiness and server refusal read the same in both places, and a
"View image tags" link to see the new image. In the drawer the button
is 44px tall below sm (a new touchTarget option); tables keep the
compact pill.

The drawer rebuilds its inventory rows on every refresh and clock tick,
which would recreate the button and close an open review dialog. The
controls are now one element created once under the drawer's owner and
reused by every rebuild; tests hold a review dialog open across a
refresh and a minute of clock time.

Reviewed by Sol from desktop and phone screenshots; its points (tap
size, stability, a fixture showing real rather than loading states,
wrapping inside the card) are all addressed.
2026-10-14 15:16:45 +01:00
rcourtman 48f0dc9e82 Colour a container drawer's status dot by its health, as the row does
A running container failing its health check showed a red dot and
"Unhealthy" in its table row but a green dot in the expanded drawer
header, which used the generic run-status mapper for every resource.

The drawer header now uses the container row's mapper for app
containers, so run state and health check decide the dot and its label
in both places. Other resources keep the shared run-status dot. Sol
reviewed the desktop and phone screenshots and found the dot plus the
lifted "Health check: Failing" row enough, without another label.
2026-10-14 14:38:24 +01:00
rcourtman b6e1fd18bc Give agent, container and Docker host drawers the Proxmox Overview
The Proxmox guest and node drawers lead with alerts, lift anything wrong
into Worth checking, show the charts, one facts line and a closed Details
section. The other object drawers still opened large always-visible
cards: an agent's Operator Context card holding only an IP, a
container's digests and label inventory, five Docker host cards that
mostly restated the row.

The shared resource drawer and the Docker host drawer now follow the
same Overview. A "No active alerts" line, rows lifted in front, the
charts, a facts line that adds what the row lacks (container image and
Compose service; host CPUs, RAM and disk used of total, address), and
every inventory section, provider Platform details included, under one
closed Details.

Lifting follows evidence, not only colour: an available image update
carries an explicit lift flag; an offline resource's state is
danger-toned with when it was last seen, so it is never hidden behind
Details; a row the resource's own alert already states stays in Details
instead of repeating it. Facts state used amounts only for an online,
current reading. An availability endpoint keeps its check cards in
front, since they are its whole reading.

Reviewed twice by Sol from before/after screenshots at desktop and
phone width; the second pass confirmed the first round's findings
resolved and led to the alert de-duplication.
2026-10-14 14:00:23 +01:00
rcourtman f872ec9553 Name where History readings came from instead of always "stored"
A History reply carries its source: the history store, readings Pulse
holds in memory because the store has none for the range, or a lone
current reading when no history exists yet. The drawer readout ignored
it and always said "Latest stored", so memory and live readings were
presented as stored history. The canvas charts did badge the source, but
as "Buffer" and "Live", words a reader has no reason to know.

One shared vocabulary now covers both. Stored history (and demo data)
keeps "Latest stored" / "Stored". Memory readings read "Latest recorded"
/ "Recorded", with one line above the drawer charts saying the store has
none for this range yet, and a "Recent only" badge on canvas charts. A
lone current reading reads "Current", badged "Current only".
2026-10-14 13:04:24 +01:00
rcourtman 8ae88cb6d1 Band and fold the storage History charts like the drawer charts
The drawer charts gained a min-max band (#2917) and a phone fold
(#2923); the storage disk and pool charts, drawn by the canvas
HistoryChart, had neither.

Band: each stored point is a bucket average with its lowest and highest
reading. When any bucket spreads, the chart now draws a 20% band between
them behind the line, so a short spike survives downsampling. The area
fill under the line is gone, so storage charts read like the drawer
charts; a flat series is a line alone.

Phone fold: a disk detail stacked five charts on a phone. It now shows
the first History chart plus any chart whose SMART card is out of
bounds, and folds the rest, Live I/O included, behind one button. Folded
charts read nothing until opened. The button is a shared
PhoneChartsToggle that the drawer History now uses too.

The visual pass also caught short windows repeating a time label
("Oct 2" four times); the time axis now states each label once.
2026-10-14 12:52:09 +01:00
rcourtman ed5914812c Stop History chart axis labels crowding or repeating
The canvas History chart always drew four time labels, so in a narrow
card, such as a disk's live I/O charts three abreast, they ran together
("12:04 PM12:07 PM12:10 PM12:14 PM"). It now measures its labels and
draws as many evenly spaced times as the plot fits, down to the first
and last.

The drawer charts format sub-kilobyte rates as "<1K/s" on purpose, so a
quiet link printed "<1K/s" on two gridlines, which tells the reader
nothing. Both chart kinds now blank a middle gridline label that repeats
a neighbour; the gridline stays and the end labels always remain.

The range-label test imported two storage modules inside its body and
timed out under load; they are now static imports.
2026-10-14 12:20:36 +01:00
rcourtman ef486d54e6 Keep History chart readings through a resize
A History chart sizes its point cap from its width, and the cap was part
of the selection that decides when a chart starts over. Any resize, a
window drag, a drawer or sidebar change, even a full-page screenshot,
cleared the readings and showed a "Loading history" spinner until the
re-read at the new cap returned.

The cap now only shapes the request. The series a chart shows (target,
metric, range, organisation, lock) decides when it starts over, so a
resize re-reads the same series quietly with its readings on screen.
Pointer inspection still resets on a resize, because its pixel positions
moved.

The guest-storage-2263 browser proof now narrows and restores the
viewport and takes a full-page capture with a disk open: the old hook
showed spinners, the new one keeps the charts.
2026-10-14 11:58:46 +01:00
rcourtman c94aec835f Pause a guest's disk charts while Manage hides its Overview
The guest drawer keeps Overview mounted behind the Manage tab so
switching back does not jump. Its own history charts already paused
there, but an expanded physical disk's charts kept polling every 10-30
seconds and the disk list kept refreshing on live updates, all for a
view nobody could see.

HistoryChart now takes enabled: false aborts its read and stops polling
while keeping the readings it shows, and resuming the same selection
reads at once without a loading spinner before polling again. The
drawer's historyEnabled reaches DiskDetail through GuestPhysicalDisks,
which also holds the list's realtime refresh through
useUnifiedResources' realtimeEnabled.

The guest-storage-2263 browser proof now hides and shows the Overview
with the clock running: the old code read disk history while hidden,
the new code reads none and resumes with the same chart nodes.
2026-10-14 11:47:03 +01:00
rcourtman 3a69ad0906 Keep Retry focus handoff working in Chromium; repair seven proofs
The focus handoff from #2925 tracked Retry focus with focus/blur
handlers. Chromium blurs a focused element when it leaves the DOM, and
the Show branch removes the button before the error effect runs, so the
flag was already false and focus fell to the page. jsdom never blurs on
removal, which is why the unit test passed. Read document.activeElement
when the Retry branch is disposed instead, and make the refresh test
model the removal blur so it fails against the old code.

The worker-browser proofs for history response, pointer, inspection,
window and the three PBS fixtures still clicked the removed History tab
and Refresh button. They now read the charts on Overview, drive reads
through the 30-second poll, select series paths rather than every path
(bands and dots add paths), and mount fixtures in a shared app runtime
(fixtureRuntime.tsx) because drawers read the WebSocket and theme
contexts. The PBS refresh proof is what caught the focus regression.
2026-10-14 11:09:55 +01:00
rcourtman 86297b67ea Drop the history Refresh button and chart only a resource's own alerts
A final review of the drawer series (screenshots plus the full diff)
and a real alert on the preview found several problems:

- A Docker host drew its container's 85% memory trigger on the host's
  own memory chart and turned the host reading red. Needs attention
  rightly lists nested alerts; the charts now take triggers only from
  getOwnAlertsForUnifiedResource, the resource's exact-key alerts.
- A container drawer listed none of its own alerts. The matcher now
  answers to docker:<host>/<container id> (or /name:<name>), the key
  the alert engine writes, so it lists them and charts the trigger.
- Docker and resource-detail drawers printed the alert's breach-time
  message ("memory at 99.5%") beside a chart reading 70%. They now use
  the shared attention copy the Proxmox drawers use: "Memory 70% now,
  recovering".
- Disk temperature and storage capacity alerts reached no chart; an
  alert line drawn before any history sat at an arbitrary height; the
  dashed line had no visible label (the legend now says "alert at
  85.0%"); a phone panel shown for an alert hid again when the alert
  cleared, under a reader mid-inspection; facts strips stated a used
  memory amount for a retained reading.

The Refresh history button is gone: the charts poll every 30 seconds.
Retry history still appears for a failed read, and when a successful
retry removes it, focus moves to the history region instead of the
page. Tests that used Refresh to trigger a read now advance the poll on
a fake interval clock.
2026-10-14 10:23:01 +01:00
rcourtman ac75d4425a Repair the PBS drawer browser fixture and its two proofs
The drawer tab-retention proof (#1723) and the PBS host history
correlation proof share browser-tests/pbs-host-history-correlation.tsx.
That fixture mounted the Backups table without the app runtime context
the drawer Overview reads alerts from, so both proofs threw before the
drawer rendered and had silently stopped proving anything.

The fixture now supplies an explicit offline WebSocket and dark-mode
context and a router, as the other drawer fixtures do. The correlation
proof follows the drawer's current shape: history charts sit on
Overview, so it checks that Overview stays selected with stable chart
paths across refreshes. Since the Identity card shows each identifier
once, the Metrics Target row may be absent; when present it must name
the host series, and the intercepted history request still must.

Both proofs pass at 1280 and 390px on every topology. Negative controls
fail as they should: resetting the tab when the metrics target drops
breaks the retention proof, and charting the PBS service key breaks the
correlation proof.
2026-10-14 09:02:31 +01:00
pulse-triage[bot] 90a8b890e0 Show one chart panel in phone drawers and the rest on request (#2923)
Stacked on rcourtman/Pulse#2921, which sits on rcourtman/Pulse#2920;
this branch contains both. It lands after them or together with them.

With history charts on every drawer's Overview, a phone stacked three or
four chart panels before a guest's facts, a node's hardware or an
agent's details. At 390px a guest drawer was 1043px tall.

Below the `sm` breakpoint, the shared drawer chart component
(`GuestDrawerHistory`, used by the guest, node, Docker host and
resource-detail drawers) now shows:
- the first panel (Utilization), plus any panel carrying an active
alert;
- one full-width "Show Network I/O, Disk I/O, Thermals" button
(`aria-expanded`) that reveals the rest and then reads "Show fewer
charts".

Wider layouts are unchanged. It is a CSS class on panels that stay
mounted, so it adds no read.

Measured at 390px on the built preview:

| Drawer | Height | Notes |
| --- | --- | --- |
| Guest | 657px (was 1043px) | |
| Node | 597px | 3 panels collapsed |
| Agent | 715px | 3 panels collapsed |

At 1440px every panel shows and there is no toggle.

Tests: the new cases in `GuestDrawerHistory.inspection.test.tsx` cover
the collapse, the toggle, and an alerting panel staying visible.
Typecheck plus the Workloads, Infrastructure, Docker and Storage suites
pass on pulse-dev.
2026-10-14 01:43:11 +01:00
rcourtman e735c464da Name history ranges the same way in every range picker (#2921)
Stacked on rcourtman/Pulse#2920 (this branch contains its commit); it
lands after or together with that PR.

The drawer range picker said "24 hours", the physical disk detail said
"Last 24 hours" and the storage pool detail said "24h".
`historyChartModel.ts` now owns one label table,
`formatHistoryRangeOptionLabel`, and all three pickers take their option
names from it: "1 hour", "24 hours", "7 days". That matches how the rest
of the app names durations (system settings, Patrol schedule).

The chart header's compact segmented range buttons keep their short
"24h" tokens. They are a different control, and they are hidden inside
these details.

Proofs: option text is asserted in
`GuestDrawerHistory.inspection.test.tsx` and `DiskDetail.test.tsx`. A
guardrail stops a local label list from coming back, and
`historyChartModel.branchcov2.test.ts` checks the disk and pool option
lists against the shared table. In the built preview, I read the option
text of the guest drawer, pool and disk pickers.
2026-10-14 01:05:14 +01:00
pulse-triage[bot] f49cbaec62 Release v6.6.0-beta.3 (#2922)
Release preparation for `v6.6.0-beta.3`. After this merges, the release
workflow runs at this exact head
`0f7bdda87868e5f9eed0c1deb3bf8ef0f15f82fe`.
2026-10-13 23:51:05 +00:00
rcourtman da74fb4209 Show one chart panel in phone drawers and the rest on request
With history charts on every drawer's Overview, a phone stacked three or
four chart panels before a guest's facts, a node's hardware or an
agent's details: a guest drawer ran 1043px tall at 390px wide.

Below the sm breakpoint the shared chart component now shows the first
panel (Utilization) and any panel carrying an active alert, then one
full-width "Show Network I/O, Disk I/O, Thermals" button that reveals
the rest. The guest drawer drops to 657px. Wider layouts are unchanged.
It is a CSS class on panels that stay mounted, so it adds no read and
the hidden panels keep their data for when they are shown.
2026-10-14 00:21:18 +01:00
pulse-triage[bot] 0f7bdda878 Prepare v6.6.0-beta.3
Beta cut from main at 1844f6c0ed. It carries every change merged since stable v6.5.0.

Contract-Neutral: Release-only v6.6.0-beta.3 version, release notes and distribution metadata; no public install or runtime contract changes.
Change-source: pulse-maintainer
2026-10-14 00:10:09 +01:00
rcourtman 5f046de046 Name history ranges the same way in every range picker
The drawer range picker said "24 hours", the physical disk detail said
"Last 24 hours" and the storage pool detail said "24h", so the same
choice read three ways depending on where a reader opened history.

historyChartModel now owns one label table, and the drawer, disk and
pool pickers all take their option names from it: "1 hour", "24 hours",
"7 days". That matches how the rest of the app names durations (system
settings, Patrol schedule). The chart header's compact segmented range
buttons keep their short "24h" tokens; they are a different control and
are hidden inside these details.
2026-10-13 23:55:15 +01:00
rcourtman f0108e4930 Show storage pool and disk history in the detail itself
Every resource drawer now charts history on its Overview. The storage
page's pool and physical-disk details were the last to keep it behind a
History tab, and that was their only other tab, so a reader opened a
pool or disk and then had to switch tabs to see whether it was filling
up or heating.

Both now render one view with no tab strip. A pool detail leads with its
Capacity Trend card, the range picker in the card header, followed by
configuration, topology and devices. A disk detail keeps its health
verdict and SMART facts first, as the evidence behind the verdict, then
its History charts with the range picker beside the heading, then live
I/O.

The storage contract had required these charts to stay behind the
History tab; it now records the 2026-10-13 decision instead.
2026-10-13 23:42:48 +01:00
rcourtman 5168ae6460 Chart history on Overview in Docker host and resource drawers
PR 2913 moved the Proxmox guest and node drawers' charts onto Overview
and removed their History tab. The Docker host drawer and the shared
resource-detail drawer (agent machines, containers, PBS, TrueNAS,
Kubernetes and other platform rows) still kept the charts a tab away,
so the same kind of drawer behaved two ways depending on the page.

Both now render the history charts on Overview under the attention
list, opening on 24 hours with the range picker beside them, and drop
the History tab. Charts stop polling while another tab shows, and an
active alert on a charted metric draws its trigger the same way as in
the Proxmox drawers. A resource whose metrics target drops for one
snapshot simply loses its charts until the target returns; the
selected tab is kept.

Storage page disk and pool details keep their own History tab; they
are separate components and their Overview has no charts to repeat.

E2E specs 27 and 63 no longer click History; the drawer read starts
when it opens. The manual tab-retention proof now selects Manage. Its
fixture page fails before the drawer renders (no WebSocket provider)
on main as well, so it was not run.
2026-10-13 23:30:46 +01:00
rcourtman 77131c19b1 Show each bucket's min-max range behind drawer chart lines
Drawer charts plot one average per time bucket, so a short CPU burst or
I/O spike inside a 6-minute bucket (24h) or an hourly rollup (7d) shrinks into
the average and disappears. The trend cards this Overview replaced
stated a "24h low-high" range; the charts had nothing in its place.

Each stored point already carries its bucket's min and max. A tinted,
outlined band now spans those extremes behind every line, so a spike
stands as a spire above the average. Series whose buckets never spread
draw no band. While a time is inspected, the legend names that bucket's
peak when it rises at least a tenth of the plot above the average, and
the accessible value states each reading's average and range.

Spec 86 also allows sub-pixel clipping when it checks that the drawer's
Memory rows are fully in view. With the charts on Overview, CI placed
that table on a fractional offset and the rounded table border clipped
0.4% of it (ratio 0.9956), failing an assertion of exactly 1.
2026-10-13 22:59:35 +01:00
rcourtman b837a8a98d Chart drawer history on Overview and drop the History tab (#2913)
Opening a Proxmox guest or node drawer showed 24-hour trend cards for
CPU, memory and disk on Overview, then a History tab with the same
metrics in a different chart style, opening on 7 days, plus network,
disk I/O and thermals. One tab was a superset of the other.

This charts history on Overview itself and removes the History tab from
the guest and node drawers (tabs are now Overview and Manage):

- Overview reads: alerts line, Worth checking, the charts (24 hours by
default, range picker beside them), disks and sensors, facts strip,
closed Details.
- Charts poll only while Overview is shown, and show the live reading
(qualified current or last known) until history is stored. An offline or
stale node says so in one line and offers no live values.
- An active alert on a charted metric tones its reading and, with a
numeric trigger, draws a dashed line. I/O triggers are converted from
the alert engine's MiB/s to the charts' B/s.
- The facts strip carries the amounts behind the percentages: guest
vCPUs and used-of-total RAM and disk; node used-of-total RAM and root
disk.
- The range picker mints its id once per instance (a guest and a node
drawer can be open together). Legend readings are larger, and the charts
use two columns at medium widths.

Docker host and resource-detail drawers keep their History tab for now.

Verification: typecheck and the Workloads, shared, Proxmox, Docker and
Infrastructure suites (305 files, 5752 tests) on pulse-dev.
Full-resolution screenshots of the built bytes at 1440, 1000 and 390px;
the receipt lists what was exercised. The dashed alert line was not seen
in the browser (no Proxmox guest or node had a metric alert in the mock
estate) and is covered by unit tests.
2026-10-13 22:31:44 +01:00
rcourtman 070d0a36fb Chart drawer history on Overview and drop the History tab
Opening a Proxmox guest or node drawer showed 24-hour trend cards for
CPU, memory and disk on Overview, then a History tab with the same
metrics again, in a different chart style and opening on 7 days, plus
network, disk I/O and thermals. One tab was a superset of the other,
so the reader had to reconcile two views of the same data.

The guest and node drawers now chart history on Overview itself, under
the alerts line and Worth checking, opening on 24 hours with the range
picker beside the charts. The History tab and the trend cards are gone.
The charts stop polling while Manage is shown, show the live reading
(qualified current or last known) until history is stored, and an
offline or stale node says so in one line instead of offering live
values.

What the cards carried and the charts lacked moves with them:
- An active alert on a charted metric tones its reading and, when it has
  a numeric trigger, draws it as a dashed line. I/O triggers are
  converted from the alert engine's MiB/s to the charts' bytes per
  second, and networkIn/networkOut map to the netin/netout series.
- The facts strip states the amounts the percentages are of: guest
  vCPUs and used-of-total RAM and disk, node used-of-total RAM and root
  disk (capacity only while a node's readings are withheld).

The range picker now mints its id once per instance, so a guest and a
node drawer open together no longer share a label target. Legend
readings are a step larger and the timestamp quieter, and the charts
use two columns at medium widths. Docker host and resource-detail
drawers keep their History tab for now.
2026-10-13 22:15:36 +01:00
rcourtman 316f4d5146 Record the daily beta release cadence
Capture the requested daily previews in the canonical release policy and
resolved decisions while retaining qualification and RC promotion rules.
2026-10-13 22:10:51 +01:00
rcourtman 72d25e4925 Drop guest row captions that repeat an empty cell (#2912)
The Workloads guest table put a second line under memory and disk cells
that repeated an empty cell and gave no reason: "N/A / Unavailable" and
"- / Unavailable" on every stopped guest, "Unavailable" under every
agentless VM's disk, and "May include cache" under every agentless VM's
memory bar regardless of value.

What changes:

- Memory that cannot be read renders the same dash as an unreadable
disk. Its reason stays in the cell title, the sparkline's hover title
and assistive text. Sparkline accessible values are unchanged.
- Disk shows a visible label only for a retained reading (Last known /
Prior) or a read the user can fix (No permission / Agent error, amber).
Stopped VMs, missing guest agents and lock deferrals show the dash.
- "May include cache" appears only when the guest's own memory is at its
warning or critical threshold (the figure the bar is coloured by, in
host-share mode too), the case from #2762 and #2639. Below that, the
caveat is title and assistive text.
- Unavailable memory explanations name what is known rather than
"Unknown source"; a powered-off guest reads "Unavailable while the guest
is powered off."
- `MetricMiniSparkline` gains an optional `tooltip` so the chart
covering the cell carries that reason without changing its accessible
name.

User guide: `docs/VM_DISK_MONITORING.md` (and its shipped mirror) now
describes the labels the row shows, and
`test_guest_disk_reading_docs.py` pins them. This replaces #2893, whose
docs recipe test still expected the old labels.

Contracts: `frontend-primitives.md` and `performance-and-scalability.md`
describe the new caption rules. Four browser proof scripts and
`86-hybrid-memory-browser.spec.ts` are updated to the new behaviour; the
proof scripts need their container harness and were not run here.

Verification: Workloads suites (106 files, 2160 tests),
`memoryObservation` and `MetricMiniSparkline` tests, lint, Prettier and
type check pass. Browser receipt `6e39cc0902eefe58.json` records a live
pass on a mock stack built from main, in Bars and Trends at 1900x1100
and 375x812: captions only on the cache VM at warning, dashes on stopped
and agentless rows, rows 32px / 44px, no overflow.
2026-10-13 21:29:21 +01:00
rcourtman 160701bb42 Drop guest row captions that repeat an empty cell
The Workloads table put a second line under memory and disk cells on
every stopped guest ("N/A / Unavailable", "- / Unavailable"), on every
agentless VM's disk, and "May include cache" under every agentless VM's
memory bar, even at 20%. None of them said why or what to do, and they
broke the row rhythm.

Memory that cannot be read now renders the same dash as an unreadable
disk, with its reason in the cell title, the sparkline's hover title
and assistive text. Disk shows a visible label only for a retained
reading (Last known) or a read the user can fix (No permission, Agent
error). The cache caption appears only when the guest's own memory is
at its warning or critical threshold, the figure that colours the bar
in host-share mode too, which is the case #2762 and #2639 were about;
below that the caveat stays in the title and assistive text.

An unavailable memory explanation names what is known instead of
"Unknown source": a powered-off guest reads "Unavailable while the
guest is powered off." MetricMiniSparkline gains an optional tooltip
so the chart covering the cell can carry that reason without changing
its accessible name.
2026-10-13 21:08:59 +01:00
rcourtman 651ae94b31 Integrate update-access guidance with the current node drawer
Preserve main's neutral denied-access state, wrapped Details and current
node metrics while retaining permission help across reading updates.
Verify disclosure dismissal, responsive placement and shipped docs.

Refs #1802
2026-10-13 20:54:39 +01:00
rcourtman 4f14ad6ab9 Use one primary CPU temperature rule everywhere
Three rules chose a node's single CPU temperature. Node history took
CPUMax first, agent history took cpu_package first, and the node drawer
recomputed the maximum of package, CPUMax and every core in the
browser. On an agent-fed node CPUMax is built from cores alone and can
sit below the package, and elsewhere a core can run above the package,
so the drawer's CPU temperature headline could disagree with the line
drawn beneath it and with the History tab. Node alerts and the CPU
low/record tracking used yet another package-first rule.

models.PrimaryCPUTemperatureCelsius is now the one rule: the hottest
usable package, CPU-maximum or core reading, with a sensor-map form for
agents. Node and agent history, the router's live fallbacks, the
unified resource temperature, node temperature alerts and the CPU
low/record tracking all call it. The drawer reads the resource value
through a new Node.cpuTemperature field instead of recomputing it, and
getCpuTemperature is removed.

Mock mode wrote a second package reading under a cpu.package key into
every agent host, including node-linked ones, from a series unrelated
to the node's static reading. A node and its linked agent now carry one
reading, sampled from the agent's series under the agent's real keys,
and both histories seed from that series, so the current reading
continues the line.
2026-10-13 20:15:36 +01:00
rcourtman 526fa790ae Make drawer charts and attention readable at a glance (#2910)
Stacked on #2909 (its commit is the parent here and lands with either
PR).

A full-resolution visual pass of the guest and node drawers, plus a Sol
review of the same screenshots, found the charts and attention hard to
read:

- **Numeric axes.** History I/O axes read "Max / Avg / 0" with no
magnitude, and "Avg" was the scale midpoint, not a measured average
(demand ledger "Numeric axis labels on history I/O charts", #1720).
Thermals read "Max / Mid / Min". Gridline labels now state the scale
value (e.g. `5.0M/s`, `60°C`) and sit centred on their gridlines.
- **Worth checking up front, one line each.** It was a full card per
section below the charts. It is now a compact list directly under the
alerts line, using the shared detail-row tone.
- **Now vs history.** Trend card headlines say "now", and the range
reads "24h low–high", so the live number is not read as the last point
of the stored line.
- **No text wall.** The two-sentence memory-cache caveat under the
charts (repeated in Details) becomes "may include cache" on the Memory
card; the full note stays once, in Details.
- The missing-readings note is one line, and the Details summary uses
readable text.

Tests: the full frontend suite passes (15,467). New proofs: Worth
checking renders above the trend cards with the shared tone, and the
guardrail pins that the list reuses `detailValueToneClass`. Browser
receipt: full-resolution screenshots of every drawer tab at 1440 and
390px, reviewed.

Follow-ups not in this PR (from the same review): threshold lines on
History charts, a crosshair shared across panels, taller History plots,
a Manage tab layout pass, and status text beside the drawer's status
dot.
2026-10-13 16:23:46 +01:00
rcourtman d5ff029517 Treat a denied update check as a setting and wrap node details (#2909)
- **Denied update check is a setting, not a fault.** The node drawer
listed "Updates: Unavailable · Update check access denied" in amber
under Worth checking. Proxmox gates the package list on `Sys.Modify`,
and `docs/TROUBLESHOOTING.md` calls keeping the narrower monitoring role
a valid choice, so `permission_denied` now reads neutral and stays in
Details. A source outage or failed check is still a warning and still
surfaces under Worth checking.
- **Node Details values wrap.** The update reason, linked agent ID and
CPU model were cut off behind an ellipsis that only a hover could
reveal. Details rows now wrap them.

Verified in headless Chromium against a mock-mode build, with
`/api/resources` rewritten to inject each update state, reviewing
full-resolution screenshots at 1440 and 390px. Tests: unit tone tests, a
drawer test that keeps the denied check in Details with a wrapped value,
and a failed-check test that keeps it under Worth checking. Putting the
tone back to warning fails two of them. 813 node, Proxmox and guardrail
tests pass.
2026-10-13 16:11:33 +01:00
rcourtman 2e71658174 Keep one timeline per resource in mock chart history (#2908)
Mock chart history thinned each metric with LTTB on its own. LTTB keeps
value-dependent points, so the metrics of one resource ended up with
different timestamps. The public demo (6.5.0) showed this: a container's
24h history had eight ~48-point series spread over 162 distinct
timestamps, and a node's had 166. The drawer History panels break a line
wherever another series in the panel has a reading, so every mock chart
drew as dots with "Missing observations". Real poller history writes all
of a resource's metrics at the same times.

Mock series are now thinned onto an epoch-aligned grid. Each bucket
keeps its first reading, the newest reading is kept too, and every point
keeps its own timestamp. Point targets and values are unchanged.

Verification:
- `TestMockChartHistorySharesTimestampsAcrossMetrics`
(`monitor_metrics_slo_test.go`) checks a mock VM, container and node, on
synthetic and seeded history, at 1h, 4h, 24h and 7d. It fails on the
previous code (5 of 20 memory timestamps shared with cpu) and passes
now. `internal/monitoring` passes in full.
- On a mock-mode build of this branch, `/api/metrics-store/history`
returns a timestamp union equal to the per-series count for a VM, a
container, a node and an agent at 1h, 24h and 7d. The drawer History tab
draws one continuous path per series with no "Missing observations". The
unpatched build of the same estate showed 82 dots and the note on all
three panels.
2026-10-13 16:04:43 +01:00
rcourtman 669ff81ba4 Make drawer charts and attention readable at a glance
A full-resolution visual pass of the guest and node drawers (and a Sol
review of the same screenshots) found the charts and attention hard to
read:

- History I/O axes read "Max / Avg / 0" with no magnitude, and "Avg"
  was the scale midpoint, not a measured average; thermals read
  "Max / Mid / Min". Gridline labels now state the scale value at each
  line (e.g. 5.0M/s, 60°C) and sit centred on their gridlines.
- "Worth checking" was a full card per section below the charts, so a
  21-day-old backup took three lines and read last. It is now a compact
  label/value list directly under the alerts line.
- Trend card headlines could be read as the last point of their line.
  Each now says "now", and the range reads "24h low–high".
- A two-sentence memory-cache caveat (with unrelated agent-setup advice)
  sat under the charts and again in Details. The Memory card now says
  "may include cache"; the full note stays once, in Details.
- The missing-readings note shrinks to one line, and the Details
  summary uses readable text.
2026-10-13 15:58:50 +01:00
rcourtman 19f1052321 Treat a denied update check as a setting and wrap node details
The node drawer listed "Updates: Unavailable · Update check access
denied" in amber under Worth checking. Proxmox gates the package-update
list on Sys.Modify, which is broader than monitoring needs, and
docs/TROUBLESHOOTING.md calls keeping the narrower role a valid choice.
A deliberate setting is not a fault, so that reason now reads neutral
and stays in Details. A source outage or failed check is still a
warning and still surfaces under Worth checking.

Node Details rows also wrap their values instead of truncating them.
Details is where a reader goes for the full value, yet the update
reason, the linked agent ID and the CPU model were cut off behind an
ellipsis that only a hover could reveal.
2026-10-13 15:41:23 +01:00
rcourtman 588b48b4cf Keep drawer History dots round and its readout steady (#2907)
Two drawer History presentation fixes, both reported while previewing
the drawer redesign:

- **Oval dots.** The plot stretches its viewBox to the panel
(`preserveAspectRatio="none"`), so the `<circle>` markers for single
observations and the hover cursor rendered as wide ovals. They are now
zero-length, round-capped strokes with a non-scaling stroke, so they
stay round at any width. Series lines carry `data-history-series` so
tests can tell lines from dots.
- **Jumping note.** "A dash means no stored observation at this time"
appeared and vanished as the inspected time changed, pushing the chart
up and down while scrolling past it. A missing value now reads **no
reading** in the readout itself, and the line is gone. In a pointer
sweep the chart height stayed constant (202px at 1024, 327px at 390).

Tests: History tests select lines by `path[data-history-series]` and
hover markers by `[data-history-hover-marker]`. A new guard checks the
plot has no `<circle>` and that dots are round non-scaling strokes. The
readout assertions now expect "no reading". Workloads, Infrastructure
and Docker suites pass (3,692 tests). Contract notes in
`frontend-primitives` and `performance-and-scalability`.
2026-10-13 15:39:52 +01:00
rcourtman b4be3bd55b Keep one timeline per resource in mock chart history
Mock chart history thinned each metric with LTTB on its own. LTTB keeps
the points whose values stand out, so cpu, memory and disk of one guest
kept different timestamps: on the public demo a 24h container history
had eight ~48-point series spread over 162 distinct timestamps. The
drawer History panels break a line wherever another series in the panel
has a reading this one lacks, so every mock chart drew as dots with a
"Missing observations" note. Real history does not do this because the
poller writes all of a resource's metrics at the same times.

Thin mock series onto an epoch-aligned grid instead: keep the first
reading in each bucket plus the newest reading, at their own
timestamps. Which readings survive now depends only on when they were
taken, so series sampled together keep identical timestamps, values
stay real readings, and point targets are unchanged. The synthetic
generator no longer pre-thins to 500 points, since every caller thins
onto the grid.
2026-10-13 15:28:57 +01:00
rcourtman 5bfa246856 Keep drawer History dots round and its readout steady
The History plot stretches its viewBox to the panel width
(preserveAspectRatio="none"), so the <circle> markers for single
observations and the hover cursor rendered as wide ovals. They are now
zero-length, round-capped strokes with a non-scaling stroke, drawn in
screen pixels, so they stay round at any width. Series lines carry
data-history-series so tests can tell lines from dots.

The readout explained a missing value with a separate line, "A dash
means no stored observation at this time", which appeared and vanished
as the inspected time changed and pushed the chart up and down, most
visibly while scrolling past it. A series without a reading at the
inspected time now says "no reading" in the readout itself, and the
extra line is gone.
2026-10-13 15:22:12 +01:00