Commit Graph

5742 Commits

Author SHA1 Message Date
rcourtman d6ccdd6943 Say why TrueNAS storage is unhealthy, drop the healthy pills
The Storage tab put a green Healthy pill on every pool, dataset and disk,
repeating the status dot, while the few amber rows gave no reason. The
pool said Warning where the dataset said Attention for the same bucket,
and in the disk scope the pill clipped past the phone edge.

Healthy rows now leave Health empty. A non-healthy row says why in the
Health column, on the row's single line as the shared platform-table
rhythm requires: TrueNAS's own alert text first, Pulse's risk summary when
TrueNAS raised no alert, then the dataset state tag (read-only datasets
raise no incident, so the tag is the provider's only reason), and a plain
fallback so no amber row is silent. Stale TrueNAS data leads when it is
the cause. A count stands in for further reasons, with every reason on
hover and for screen readers. Health sorts by severity and is
desktop-only in every scope, so phones keep the status dot and the drawer.
2026-10-05 06:37:24 +01:00
rcourtman f30be8b48f Say which TrueNAS services and VMs should be running
The Services tab gave a red dot to SMART with no reason, beside the same
Stopped pill as SSH, and spent a column on process IDs. The VMs tab spent
two columns on the bootloader and device counts. None of those columns
answers whether something is wrong.

A service stopped while set to start at boot, and a VM set to autostart
that is stopped, now say "should be running" beside the state, with the
full sentence on hover. Services also gain a drawer Condition row, since
the State column is hidden on phones. A VM stopped without autostart gets
no note, because someone stopped it on purpose. Process IDs, bootloader
and device counts stay in the drawers. Rows stay single-line, as the
shared platform-table rhythm requires.
2026-10-05 06:13:52 +01:00
rcourtman 94ac386abf Make availability checks agree across Settings and Machines (#2493)
Settings said "2 down" where Machines said "1 offline, 3 need
attention" for the same checks. Settings counted any failed latest
probe as down, while Machines and the alert engine count a check offline
only once it has failed its threshold number of times in a row.

Settings now classifies each check with the Machines status of its
resource when that is loaded, and otherwise reads the target the way the
availability poller does, so both pages report the same counts in the
same words. A check that is failing but not yet offline is amber, with
the threshold on hover.

The Machines table shows 24h uptime beside the latest result (it existed
only in the Fleet view), and only when most of the window had a
determinate answer, refreshing every five minutes. Failures read "3 in a
row" instead of "3/2", polling cadence moves to the widest tables, and
the duplicate Manage link is gone.
2026-10-05 05:51:46 +01:00
rcourtman c4b3db4058 Show Mail Gateway alerts on the gateway they belong to (#2490)
A gateway Proxmox reported online could be failing its job: mail held
in the queue past the configured age. The Mail Gateway table ignored
alerts, so that gateway rendered green and counted as Healthy while the
problem sat only on the Alerts page. Alerts are keyed by the PMG
instance id and rows by the unified id, so nothing joined them.

Rows now match unacknowledged alerts across the gateway's identity
aliases. An alerted, reachable gateway reads and filters as needing
attention (red when an alert is critical) and names the alert type under
its name. Its expansion leads with the exact alert message. On phones,
six tracks truncated "mail-gateway-eu" and "mail-gateway-us" to the same
text; the node count moves into the expansion so names stay whole.
2026-10-05 05:32:49 +01:00
rcourtman 53029b08ea Make availability checks agree across Settings and Machines
Settings said "2 down" where Machines said "1 offline, 3 need
attention" for the same checks. Settings counted any failed latest
probe as down, while Machines and the alert engine count a check offline
only once it has failed its threshold number of times in a row.

Settings now classifies each check with the Machines status of its
resource when that is loaded, and otherwise reads the target the way the
availability poller does, so both pages report the same counts in the
same words. A check that is failing but not yet offline is amber, with
the threshold on hover.

The Machines table shows 24h uptime beside the latest result (it existed
only in the Fleet view), and only when most of the window had a
determinate answer, refreshing every five minutes. Failures read "3 in a
row" instead of "3/2", polling cadence moves to the widest tables, and
the duplicate Manage link is gone.
2026-10-05 05:31:04 +01:00
rcourtman 73c9e15491 Merge remote-tracking branch 'origin/main' into fix/mail-gateway-says-why 2026-10-05 05:08:03 +01:00
rcourtman b10b52021e Move the Mail Gateway drawer note into the drawer hierarchy clause
The note sat before the same frontend-primitives heading another lane used and conflicted with main. Taking main's file with the note folded into the existing object-drawer clause lets main merge cleanly.
2026-10-05 05:06:28 +01:00
rcourtman 43c38253b6 Explain backup protection in plain words
The Coverage tooltip and expansion render the posture explanation
verbatim, and it read like the model: "a current subject-linked
backup", "older than the configured freshness window", "a newer
provider failure", "a stronger protection claim".

Each explanation now says what Pulse found in plain words and stays true
to its branch: the stale case names the window ("the newest confirmed
backup is older than 7 days"), the failure case no longer claims a
timing it cannot know, and partial history without a backup no longer
says a backup exists. The window formatter never rounds a sub-hour or
fractional window up. The frontend fallbacks for checking, unrated and
unknown rows and the snapshot-only tooltip use the same voice.
2026-10-05 05:03:08 +01:00
rcourtman a82a8e0474 Lead the Kubernetes overview with the nodes that are not Ready (#2489)
The Kubernetes overview said "2/3 nodes" in amber on the cluster row
but no surface on the page named the node, and active health signals
rendered at the bottom, after every healthy deployment, pod and
controller. On the Nodes tab a node's state was a hover title on a
dot that screen readers never heard.

Health signals and the nodes that are not Ready, cordoned or degraded
now lead the overview, each only while it has rows, using the existing
Kubernetes alerts and nodes tables. The full node inventory stays on
the Nodes tab, and the attention list keeps its own sort. The nodes
table names the state in a Status column beside the name, as kubectl
does, and announces it on phones where that column is demoted.
2026-10-05 04:40:56 +01:00
rcourtman 20eed2127b Show Mail Gateway alerts on the gateway they belong to
A gateway Proxmox reported online could be failing its job: mail held
in the queue past the configured age. The Mail Gateway table ignored
alerts, so that gateway rendered green and counted as Healthy while the
problem sat only on the Alerts page. Alerts are keyed by the PMG
instance id and rows by the unified id, so nothing joined them.

Rows now match unacknowledged alerts across the gateway's identity
aliases. An alerted, reachable gateway reads and filters as needing
attention (red when an alert is critical) and names the alert type under
its name. Its expansion leads with the exact alert message. On phones,
six tracks truncated "mail-gateway-eu" and "mail-gateway-us" to the same
text; the node count moves into the expansion so names stay whole.
2026-10-05 04:34:24 +01:00
rcourtman 6df55d0d01 Say when a Docker host goes silent, and stop repeating host columns (#2482)
A Docker host whose agent stopped reporting kept showing its last CPU,
memory and disk bars as if they were live, with only an orange dot to
hint otherwise. The tables now read the backend's telemetry_stale
reason: that host's metric cells are dimmed, Uptime shows how long it
has been silent, and its container group header says "No report for 1h".

Health could only carry that reason when nothing outranked it, so a
silent host with an offline alert lost the signal. The verdict still
leads with the alert, and a stale source now adds a trailing
telemetry_stale reason. The first reason, which the state summary
reports, is unchanged.

Grouped by host, the containers table also repeated the host name and
engine on every row, about a quarter of its width. Those columns are
dropped in grouped mode and the group header names the engine once;
flat mode keeps them, and a saved sort on either hidden column no longer
reorders grouped rows.

Verified in a browser on a mock estate at 1440 and 390 with the rebuilt
backend (receipt included). Go: internal/unifiedresources, internal/api
summary and internal/monitoring health tests pass; full frontend suite
passes apart from the known Mac-only agentInstallCommand cases.
2026-10-05 04:32:23 +01:00
rcourtman b3fefdba2c Make Patrol and Actions views linkable (#2488)
Patrol's Inbox, Protection and Activity views and the Actions Open and
History views lived in component state. A bookmark, a shared link or a
reload always opened the default view, and back/forward did nothing.

Each view is now a route: /patrol, /patrol/protection,
/patrol/activity, /actions and /actions/history. Tabs navigate without
scrolling the page. Lifecycle-driven switches in Actions (a deep-linked
review whose action has settled) replace history instead of pushing.
The Patrol finding handoff scrolls to the findings once Activity has
mounted. The Inbox's ?attention= selection is read from the real address
when leaving the Inbox and restored on return, because the attention
workbench writes it straight to history and the router's copy can be
stale.
2026-10-05 04:26:54 +01:00
rcourtman 42ce399baf Lead the Kubernetes overview with the nodes that are not Ready
The Kubernetes overview said "2/3 nodes" in amber on the cluster row
but no surface on the page named the node, and active health signals
rendered at the bottom, after every healthy deployment, pod and
controller. On the Nodes tab a node's state was a hover title on a
dot that screen readers never heard.

Health signals and the nodes that are not Ready, cordoned or degraded
now lead the overview, each only while it has rows, using the existing
Kubernetes alerts and nodes tables. The full node inventory stays on
the Nodes tab, and the attention list keeps its own sort. The nodes
table names the state in a Status column beside the name, as kubectl
does, and announces it on phones where that column is demoted.
2026-10-05 04:11:16 +01:00
rcourtman 27f020f033 Lead Notifications with where alerts go (#2487)
A user opening Notifications wants to know where their alerts go and
how to send them to Discord, Telegram or email. The page opened on nine
"held, delivery not turned on" rows that repeated the paused banner,
then a fully expanded but disabled SMTP form and Apprise form, with
webhooks (where Discord, Telegram and ntfy live, and the destination
issues ask about most) near the bottom.

Unless delivery is degraded, destinations now lead: webhooks first,
described by the services they reach, then email and Apprise, then the
delivery log. A degraded queue still keeps its log directly under the
recovery controls. A switched-off email or Apprise destination is one
line saying what turning it on does, with "Show settings" to inspect the
form without enabling it. A new webhook opens on the service choice
instead of the generic JSON form, and setup steps keep their line
breaks.

Delivery health now sets its snapshot and availability in one batch.
Set separately, the attention flag briefly read a half-updated pair and
unmounted the warning mid-refresh.
2026-10-05 04:08:21 +01:00
rcourtman c2811cca6d Merge remote-tracking branch 'origin/main' into feat/docker-silent-hosts 2026-10-05 03:50:49 +01:00
rcourtman 55cd60ff85 Move this branch's unified-resources note off the end of the file
Every lane appends to the end of this contract, so a note there conflicts with each merge to main. Taking main's file with the note placed beside the Docker sections lets main merge cleanly now and later.
2026-10-05 03:49:14 +01:00
rcourtman 7a102fc57f Make Patrol and Actions views linkable
Patrol's Inbox, Protection and Activity views and the Actions Open and
History views lived in component state. A bookmark, a shared link or a
reload always opened the default view, and back/forward did nothing.

Each view is now a route: /patrol, /patrol/protection,
/patrol/activity, /actions and /actions/history. Tabs navigate without
scrolling the page. Lifecycle-driven switches in Actions (a deep-linked
review whose action has settled) replace history instead of pushing.
The Patrol finding handoff scrolls to the findings once Activity has
mounted. The Inbox's ?attention= selection is read from the real address
when leaving the Inbox and restored on return, because the attention
workbench writes it straight to history and the router's copy can be
stale.
2026-10-05 03:46:05 +01:00
rcourtman 76a1d8df49 Lead Notifications with where alerts go
A user opening Notifications wants to know where their alerts go and
how to send them to Discord, Telegram or email. The page opened on nine
"held, delivery not turned on" rows that repeated the paused banner,
then a fully expanded but disabled SMTP form and Apprise form, with
webhooks (where Discord, Telegram and ntfy live, and the destination
issues ask about most) near the bottom.

Unless delivery is degraded, destinations now lead: webhooks first,
described by the services they reach, then email and Apprise, then the
delivery log. A degraded queue still keeps its log directly under the
recovery controls. A switched-off email or Apprise destination is one
line saying what turning it on does, with "Show settings" to inspect the
form without enabling it. A new webhook opens on the service choice
instead of the generic JSON form, and setup steps keep their line
breaks.

Delivery health now sets its snapshot and availability in one batch.
Set separately, the attention flag briefly read a half-updated pair and
unmounted the warning mid-refresh.
2026-10-05 03:28:48 +01:00
rcourtman b38289b838 Merge remote-tracking branch 'origin/main' into feat/docker-silent-hosts 2026-10-05 03:10:44 +01:00
rcourtman 5f183749cc Move this branch's frontend-primitives note off the end of the file
Every lane appends to the end of this contract, so a note there conflicts with each merge to main. Taking main's file with the note placed beside the platform-window section lets main merge cleanly now and later.
2026-10-05 03:09:10 +01:00
rcourtman 2ea6b8bb83 Merge remote-tracking branch 'origin/main' into feat/vsphere-overview-health 2026-10-05 03:08:20 +01:00
rcourtman 47598c0290 Move this branch's frontend-primitives note off the end of the file
Every lane appends to the end of this contract, so a note there conflicts with each merge to main. Taking main's file with the note placed beside the platform-page sections lets main merge cleanly now and later.
2026-10-05 03:06:45 +01:00
rcourtman 5b7a9df8ac Lead alert thresholds with each group's default limits (#2484)
The Thresholds page opened on a ten-select CPU evaluation grid, and
every resource group started collapsed showing only a name and a count,
so the limits that decide what alerts were invisible until a group was
opened one at a time.

Each collapsed group header now states its defaults ("CPU 80% · Memory
85% · Disk 90%"), built by one helper from the same columns and defaults
the group's table edits. It drops metrics the engine treats as off and
hides the line while the group is disabled. Rules whose zero means a
fallback are stated as whole rules with their effective values: Docker's
restart loop and memory-limit, and backup age by its alerting days
rather than the dashboard-only fresh/stale hours.

CPU averaging becomes one collapsed line stating the effective window
and how many profile overrides exist, with the same selects inside.
Docker and Machines list their groups before Docker-only rules and the
SMART card.
2026-10-05 02:57:28 +01:00
rcourtman dc0691ab69 Restore the platform staleness contract note
Re-adds this branch's frontend-primitives note after taking main's file for a clean merge.
2026-10-05 02:49:42 +01:00
rcourtman 384bfacdfe Merge remote-tracking branch 'origin/main' into feat/docker-silent-hosts 2026-10-05 02:49:42 +01:00
rcourtman 698593426b Take main's frontend-primitives contract ahead of the merge
Main appended frontend-primitives notes at the same place this branch did. Taking main's file here lets main merge cleanly. The staleness note is restored right after the merge.
2026-10-05 02:48:07 +01:00
rcourtman 3cd0e7e0eb Restore the vSphere overview incident-table contract note
Re-adds this branch's frontend-primitives note after taking main's file for a clean merge.
2026-10-05 02:45:06 +01:00
rcourtman 2819b2caeb Merge remote-tracking branch 'origin/main' into feat/vsphere-overview-health 2026-10-05 02:45:05 +01:00
rcourtman 3998582278 Take main's frontend-primitives contract ahead of the merge
Main appended frontend-primitives notes at the same place this branch did. Taking main's file here lets main merge cleanly. The vSphere note is restored right after the merge.
2026-10-05 02:45:05 +01:00
rcourtman 0f66f5c136 Lead alert thresholds with each group's default limits
The Thresholds page opened on a ten-select CPU evaluation grid, and
every resource group started collapsed showing only a name and a count,
so the limits that decide what alerts were invisible until a group was
opened one at a time.

Each collapsed group header now states its defaults ("CPU 80% · Memory
85% · Disk 90%"), built by one helper from the same columns and defaults
the group's table edits. It drops metrics the engine treats as off and
hides the line while the group is disabled. Rules whose zero means a
fallback are stated as whole rules with their effective values: Docker's
restart loop and memory-limit, and backup age by its alerting days
rather than the dashboard-only fresh/stale hours.

CPU averaging becomes one collapsed line stating the effective window
and how many profile overrides exist, with the same selects inside.
Docker and Machines list their groups before Docker-only rules and the
SMART card.
2026-10-05 02:34:42 +01:00
rcourtman 4c266391c3 Say what backup health counts mean and drop per-source age columns
"17 attention / 10 unprotected" were counts without a rule, and users
have asked what Pulse treats as stale. The Backup health strip now
carries one line built from the server's posture policy window that
names the causes internal/recovery/posture.go evaluates for attention,
unprotected and unknown. Nothing renders until the policy loads, so the
page never shows a guessed window.

The coverage table showed up to four time columns (last backup, PBS
snapshot, PVE file, guest snapshot). A fresh green guest snapshot beside
an amber stale backup read as protection that posture does not grant.
Rows now carry the one counted backup age. The row expansion keeps the
newest point and the newest completed point of every source in its
bounded evidence list, so neither a run of snapshots nor a running or
failed job can hide the latest usable backup.
2026-10-05 02:12:17 +01:00
pulse-triage[bot] 1d4c3aeb6e Keep deferred guest readings, alerts and History truthful (#2472)
## What changes and why
Keep guest monitoring truthful while reads are deferred: retained
memory/filesystem values must not become new alert decisions, new
History observations, current filesystem health, or continuous chart
lines across missing readings. Failed or empty metadata refreshes retain
the last useful observation time rather than manufacturing freshness.
Independent current readings and measured-zero recovery keep their
existing behaviour.

Manual guest Discovery pauses during the existing backup/read
precaution, preserves saved evidence, and keeps delayed outcomes
attached to their original guest and run. The published drawer-header
link to a saved web interface remains available separately from live
Discovery. The existing TrueNAS guide and shipped copy keep credentials
and responses private and bound individual requests.

The guest work follows the prolonged freeze reported in #2439 and
reproduced downstream observation/lifecycle gaps. It does not identify
the freeze's cause or prove native recovery. The guidance repair follows
inspection of unsafe existing recipes. No new collector, schema,
retention policy, threshold or product surface is introduced by this
batch.

## Included source and reconciliation
Current published base `efe6fc21ae10a6bf173cc17e6203aaf6c97dc130` →
proposed head `1914f5fb7b278b5fc4cc10a292e909a10714a2f8` (39 introduced
commits, including additive integrations).

- TrueNAS guidance: `cadfd1f4e1d9f909a45977f94aadd4f1324e82ef` →
`d589407a743a12fe492e93785d9e24c12bbb4fb5`.
- Discovery pause/outcome ownership:
`2cb3f6a9043e0bab9c54c5b5ef57b3e1cb994781` →
`bbaee0a3f43dc7906011be5c78d684f9a1327bae`, including additive recovery
and lifecycle controls.
- Alert admission: `b0f8b96c8a159ac242b85f2d09f8dfffd5c29c7a` →
`0fd7768e9d1379f8aa0126b33d4047b4c52c0d87`.
- History writer admission: `a15920eaebea00ab8c353ddd3861b9cd7ff7d379`.
- Filesystem presentation: `2525d76c04a9834c2130e97c1a529e164a9c7796` →
`5b75f21159fda3c01fbe6688aa9cfde68779fba8`.
- Metadata freshness: `f1e221a0fb9b0804f200c13490f8526424cb7e1c`.
- Missing-observation chart presentation:
`0ad41e4c9a309ce459dd717d16d343b46efcac8a` →
`5e7e49650fcdf5131b7c5006fe487336ab660e15`.
- Platform-aware Discovery fixture:
`a2608c9fadf76c242527a636ee08968fa16ddde8`; TrueNAS recipe
reconciliation `1f0d12af85ec7c2ad8df391b8e6958e80fa98179`; additive
upstream/header composition and content-bound browser receipts through
`b01dad904c3436e70bc01588ea1546fffaa05dc6`; batch-start upstream merge
`f1c7e413bac2e3306d2169bcd069c60325e56d5f`, then exact bounded
refreshed-upstream merges `07112b0e3ab2be174a5ac7a11f87ac0837edceb0` and
`1914f5fb7b278b5fc4cc10a292e909a10714a2f8`.

The previous proposal head `6a20d106242598bd6b466e9ae888ea931d284b30` is
completed-failed, not still validating. Native run `37239183481`
incorrectly required a Linux/PVE capability on Windows/macOS; Build and
Test `37239183545` extracted zero wrapped TrueNAS recipes. Reviewed
additive fixtures correct both without weakening runtime safety. The old
head cannot land independently, so this update includes those
corrections and the coherent observation batch. Every accepted commit
identity and all ancestry are retained. Published cluster labels, saved
guest links, unique demo workload names the newer storage priority/alert
explanations, and the published Alerts overview are preserved. Bounded
refresh receipt
`2916978f4e4b49d084752e973e19a5c0f7dd19f0bf4ac60cc31875876318528d`
completed at the exact observed upstream without changing source; the
subsequent full-SHA merge composes it additively. A second successful
bounded refresh
`59f1939979347921d99e262398d5347077bfce6faf6f42535050840a3a19820b`
incorporates the subsequently published Alerts overview in the same way.
Neither is a replay of a failed or uncertain attempt.

## Validation and remaining gates
Retained evidence includes connected alert/API/producer/recovery
controls (`source-p58_phvc`), Discovery/component/build/browser checks
(`source-gy5x8buj`), installer/docs controls (`source-7s0iorys`,
`source-uo6rxs61`), metadata connected/race/vet/build proof
(`source-ji23nb_c`), and ten connected suites plus focused safety
races/vet and native-target compilation (`source-seboen4h`). Browser
content receipts cover the final seven changed frontend runtime files,
including the composed saved-link/safety header and chart gaps.

The History presentation proof `source-ualisb1k` completed 22,571
frontend tests across 1,234 files, types/lint/format/build and
bundle/integrity checks; its overall exit 1 from trailing nonexistent
optional test paths remains recorded, not converted into a whole-command
pass. Four browser groups/32 checks and eight inspected captures
separately passed. Earlier reproductions, fixture failures and
reporting-limit failures remain evidence.

Publication-gate checks:
- `source-qt6m6mf_`: seven connected TrueNAS recipe controls, full mock
suite, scoped real agent/guest safety, alert observation and
metadata/History tests pass. Backend/docs source is byte-identical in
the final composition; this evidence was not repeated after
frontend-only upstream advances.
- `source-xy1lnmgt`: types pass and 281 of 283 scoped frontend controls
pass; two first-case History tests hit the unchanged 5,000ms timeout.
Exit 1 is retained and the following build did not run. No functional
assertion failure or cause was established.
- One predeclared single-worker/file-serial comparison,
`source-17bch24o`, passes all 23 controls in those two files with
unchanged source, assertions and timeouts. This establishes isolated
functional behaviour, not a diagnosis or clearance of parallel CI.
- `source-dvlfnm5t`: refreshed storage composition passes types, 1,177
tests/56 files and the real production/embed build. The event-parser
line-bound flag is recorded; the complete 26,474 raw bytes independently
match the receipt.
- Final exact-head `source-sjjtzjqm`: types, scoped published Alerts
overview/translation/presentation controls and the real production/embed
build pass. Complete raw output is independently hash-matched and the
guest stopped.
- All executed receipts, including the failed scoped run, retain
complete output and completed guest stop. Provenance, clean source,
diff/ancestry checks, all 24 introduced non-merge commits' parent-bound
canonical guards using only their original recorded trailers, and the
final seven-file browser-content receipt guard pass.

Required exact-head administrative source review and GitHub checks must
complete before landing; this update is not a blind failed-job retry or
a CI waiver. Native overlap, independent thaw, fresh writes on every
covered filesystem, workload liveness, truthful alert delivery and safe
restoration of only prior-active services/timers remain separate. UI
clearance, saved values, an OK backup or these source tests are not
recovery, a containing release or installed/reporter acceptance.

Related to #2439


[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-05 01:09:46 +00:00
rcourtman a36f643510 Count alerting guests under Attention on the Proxmox overview (#2480)
The guest status chips put a running guest with an open warning or
critical alert under Running, because the Attention bucket looked only
at resource status and failed availability probes. With seven running
guests over a memory threshold the chips still read "Attention 0". The
bucket now also takes running guests whose canonical health names an
open alert. Backup age stays out: it has its own column and tab.

Two smaller fixes on the same page. The nodes table repeated one cluster
name in a Cluster column on every row; with a single cluster the column
is dropped and the header names the cluster, also while searching. The
VM disk "Unavailable" label was amber for every reason, mostly a missing
guest agent or a stopped VM, and its explanation was screen-reader only;
it is now muted unless the read is genuinely broken, with the reason on
hover.

Verified in a browser on a mock estate (receipt included); full frontend
suite passes apart from the known Mac-only agentInstallCommand cases.
2026-10-05 01:55:49 +01:00
rcourtman aed2f6c17d Add a bulk web-link editor to the workloads table (#2477)
Saved web-interface links make Pulse a quick way to reach the service a
guest runs, but each link had to be typed into one guest drawer at a
time. This adds one place to set many at once. It ships as a named bet
with no external ask behind it yet.

**What users get**
- An **Edit links** action in the workloads table header. It appears on
the Proxmox and vSphere guest tables, which share that surface.
- A panel listing every guest in the current filtered view (node
selection and filters carry over). Each row shows the guest's type, ID,
node, its saved link, and an IP-based placeholder.
- An **All / Without a link** filter, a count of guests that have a
link, and one Save for every change. Clearing a field removes that link.
- Invalid links block the save with the shared validator's message on
their row. A failed save stays on its row. Closing the panel keeps
unsaved drafts, and the trigger shows "(unsaved)".
- Read-only sessions do not see the action.

**How it fits**
- Saves go through the existing guest metadata API and metadata-changed
event, the same path as the drawer field, so the table updates at once.
- `getWorkloadGuestMetadataRecord` is now the single lookup for a row's
saved link, in both the table and the editor.
- Editor rows only follow live inventory while the panel is open, and
they keep unchanged row objects across ticks. A closed editor adds no
per-tick work, and an open one never remounts the input being typed in.
`WorkloadsSurface.performance.contract.test.tsx` pins this.

**Verification**
- Private mock stack at 1280, 1024 and 375px, covering node scope,
filters, invalid entry, add, correct, remove, and close and reopen.
Receipt: `frontend-modern/browser-verification/743cc04a20d534ac.json`.
- 13 new tests in `workloadWebLinksModel.test.ts` and
`WorkloadWebLinksDialog.test.tsx`.
- Full frontend lint, plus `src/features`, `src/hooks`, `src/__tests__`,
`src/components/shared`, `src/components/Workloads` and
`src/components/Storage`: 515 files and 8,675 tests pass.
2026-10-05 01:48:48 +01:00
rcourtman 97c2f6f6ac Say when a Docker host goes silent, and stop repeating host columns
A Docker host whose agent stopped reporting kept showing its last CPU,
memory and disk bars as if they were live, with only an orange dot to
hint otherwise. The tables now read the backend's telemetry_stale
reason: that host's metric cells are dimmed, Uptime shows how long it
has been silent, and its container group header says "No report for 1h".

Health could only carry that reason when nothing outranked it, so a
silent host with an offline alert lost the signal. The verdict still
leads with the alert, and a stale source now adds a trailing
telemetry_stale reason. The first reason, which the state summary
reports, is unchanged.

Grouped by host, the containers table also repeated the host name and
engine on every row, about a quarter of its width. Those columns are
dropped in grouped mode and the group header names the engine once;
flat mode keeps them, and a saved sort on either hidden column no longer
reorders grouped rows.
2026-10-05 01:46:58 +01:00
rcourtman 7097a83099 Turn the older-agent notice into one quiet line (#2479)
Every Proxmox, Docker, Kubernetes, TrueNAS, vSphere and Machines tab
opened with a three-line amber warning about hosts on an older Pulse
agent, listing names and a Show all button above the monitoring data.
An outdated agent is maintenance, not an incident, and the warning
competed with real problems for attention on every visit.

The notice is now one sentence in the info tone with the upgrade link
inline. The affected names sit behind a "Which hosts?" toggle, so every
name is still one click away and the inventory starts higher on the
page.

Verified in a browser at 1440 and 390 (receipt included); full frontend
suite passes apart from the known Mac-only agentInstallCommand cases.
2026-10-05 01:39:03 +01:00
rcourtman 906651fa59 Lead the vSphere overview with vCenter's health signals
vCenter alarms only appeared on the vSphere page's fourth tab, Health.
The Overview fetched hosts and VMs alone, so a host tinted yellow there
had no visible reason and datastore or network alarms were not on the
page at all.

While the Overview is open it now also reads the Health tab's resources,
and when vCenter reports any signals the existing health table leads the
page above the hosts, without its filter toolbar. Nothing is added when
vCenter is quiet, and filtering stays on the Health tab. This is the
pattern the TrueNAS Overview already uses for its health alerts.
2026-10-05 01:25:56 +01:00
pulse-triage[bot] 1914f5fb7b Preserve published Alerts overview alongside reviewed guest safety repairs
Compose the second successfully observed upstream advance additively, preserving reviewed ancestry and the existing alert observation contracts.

Change-source: pulse-maintainer
2026-10-05 01:21:18 +01:00
rcourtman e25a5653c3 Count alerting guests under Attention on the Proxmox overview
The guest status chips put a running guest with an open warning or
critical alert under Running, because the Attention bucket looked only
at resource status and failed availability probes. With seven running
guests over a memory threshold the chips still read "Attention 0". The
bucket now also takes running guests whose canonical health names an
open alert. Backup age stays out: it has its own column and tab.

Two smaller fixes on the same page. The nodes table repeated one cluster
name in a Cluster column on every row; with a single cluster the column
is dropped and the header names the cluster, also while searching. The
VM disk "Unavailable" label was amber for every reason, mostly a missing
guest agent or a stopped VM, and its explanation was screen-reader only;
it is now muted unless the read is genuinely broken, with the reason on
hover.

Contract-Neutral: Workload status bucket and disk label read existing health and disk-read fields; no performance-path change, so performance-and-scalability.md is unaffected.
2026-10-05 01:16:52 +01:00
rcourtman efe6fc21ae Make the Alerts overview answer what is broken first (#2476)
The overview is where a notification lands, but each active alert was a
tall tinted card with five buttons, every card repeated "Notification
delivery not turned on", and a three-row stats table sat above the list,
so three alerts fit on a 1440x900 screen and about one on a phone.

When delivery is off the overview now says so once, with the shared
paused-delivery banner (overview wording, a link to set up destinations,
Turn on delivery), and rows drop the per-alert line for those global
holds. Alert-specific holds such as cooldown or quiet hours still show.
Rows are compact: severity on a left edge, a locale-aware start age on
the overview's minute clock, Acknowledge, Snooze and Investigate
visible,
and Timeline and monitoring policy behind a More disclosure. A summary
line counts open alerts by severity beside the heading; the override
count, which is configuration, leaves the overview.

The banner copy now comes from the en/de/es catalogs, and with alerts
switched off it no longer claims Pulse is still detecting them (the
destinations tab said the same). The banner title was near invisible
(text-amber-900/25) and is readable again.

Verified in a browser on a mock estate (19 active alerts, delivery not
activated) at 1440x900 and 390x844 dark, in English and German;
More/Timeline/Less exercised. Five alerts now fit above the fold at
1440x900, up from three. Ledger: rcourtman/pulse-pro
FEATURE_REQUESTS.md, Answer-first presentation across platform pages.
2026-10-05 01:16:14 +01:00
rcourtman 4a215d4029 Load the bulk web-link panel on first open
The editor pushed the WorkloadsSurface chunk to 71.44 kB gzip, past
its 70.23 kB budget. Main already sat at 68.80 kB, so loading every
row of a panel most sessions never open was the wrong cost to put on
the table.

The table-header trigger now keeps only the open flag and unsaved
drafts and lazy-loads the panel, whose state and row model live in a
separate 3.9 kB chunk. The table's saved-link lookup moves to its own
small module so the table does not pull the editor's model back in.
WorkloadsSurface builds at 69.13 kB. Drafts still survive closing the
panel, and typing a link back to its saved value no longer counts as an
unsaved change.
2026-10-05 01:14:16 +01:00
pulse-triage[bot] 07112b0e3a Compose published storage presentation with reviewed guest observation repairs
Incorporate the exact bounded refreshed upstream while preserving the complete reviewed batch and original source identities.

Change-source: pulse-maintainer
2026-10-05 01:12:45 +01:00
rcourtman fc737b2355 Turn the older-agent notice into one quiet line
Every Proxmox, Docker, Kubernetes, TrueNAS, vSphere and Machines tab
opened with a three-line amber warning about hosts on an older Pulse
agent, listing names and a Show all button above the monitoring data.
An outdated agent is maintenance, not an incident, and the warning
competed with real problems for attention on every visit.

The notice is now one sentence in the info tone with the upgrade link
inline. The affected names sit behind a "Which hosts?" toggle, so every
name is still one click away and the inventory starts higher on the
page.
2026-10-05 01:02:30 +01:00
rcourtman 15ffd92e43 Lead the storage table with what is filling up, and say why
The storage table's default priority order compared only the incident
priority, which is zero for almost every record, then broke ties by name
with the sort direction applied, so the default view was reverse
alphabetical: an 89%-full pool sat second and an 85% one twelfth. Rows
with an open alert were tinted with no reason, while the alert engine
already knew "projected to fill in about 4 days" or "over the 85% limit".

The default order now puts open alerts first by severity, then the
record's incident priority, then usage, with ties reading A to Z under
every sort key. A highlighted row says why in its State cell: "Full in ~3
days" from the forecast alert's metadata, "Over 85% usage limit" for a
threshold alert, otherwise the alert text, in a compact form on phones.

A Ceph pool folded into the storage that mounts it used to drop its
alerts with it, so the surviving row lost the highlight. Consolidation
now carries the folded pools' alert identities onto that row.
2026-10-05 00:45:19 +01:00
rcourtman 967e51701e Add a bulk web-link editor to the workloads table
Saved web-interface links make Pulse a quick way to reach the service
a guest runs, but each link had to be typed into one guest drawer at a
time. Richard asked for a faster way to set them up; it ships as a
named bet with no external ask behind it yet.

An Edit links action in the workloads table header opens one panel
listing every guest in the current filtered view with its saved link.
Links save in one pass through the existing guest metadata API and
metadata-changed event, so the table updates at once. Invalid links
block the save with the shared validator's message on their row, a
failed save stays on its row, and closing the panel keeps unsaved
drafts. Read-only sessions do not see the action.

The table and the editor now share one lookup for a row's saved link.
Editor rows only follow live inventory while the panel is open, so a
closed editor adds nothing to the per-tick workload path.
2026-10-05 00:36:42 +01:00
pulse-triage[bot] b01dad904c Merge reviewed missing-observation drawer History presentation
Preserve exact Web candidate and retained browser content receipts alongside the canonical frontier.

Change-source: pulse-maintainer
2026-10-05 00:28:08 +01:00
rcourtman ddd6ba04c6 Make the Alerts overview answer what is broken first
The overview is where a notification lands, but each active alert was a
tall tinted card with five buttons, every card repeated "Notification
delivery not turned on", and a three-row stats table sat above the list,
so three alerts fit on a 1440x900 screen and about one on a phone.

When delivery is off the overview now says so once, with the shared
paused-delivery banner (overview wording, a link to set up destinations,
Turn on delivery), and rows drop the per-alert line for those global
holds. Alert-specific holds such as cooldown or quiet hours still show.
Rows are compact: severity on a left edge, a locale-aware start age on
the overview's minute clock, Acknowledge, Snooze and Investigate visible,
and Timeline and monitoring policy behind a More disclosure. A summary
line counts open alerts by severity beside the heading; the override
count, which is configuration, leaves the overview.

The banner copy now comes from the en/de/es catalogs, and with alerts
switched off it no longer claims Pulse is still detecting them (the
destinations tab said the same). The banner title was near invisible
(text-amber-900/25) and is readable again.
2026-10-05 00:22:22 +01:00
pulse-triage[bot] 70493f5e1c Compose the published guest drawer link with reviewed safety controls
Preserve exact upstream drawer-header Open action and every reviewed guest-observation commit. The existing Discovery precaution remains separate from the saved service link.

Change-source: pulse-maintainer
2026-10-05 00:14:56 +01:00
rcourtman 26871ef1ee Open a guest's saved web interface from its drawer header
Phone and narrow workload rows hide the adjacent web link so guest
names stay distinguishable. On a phone that left a saved service
reachable only from the URL field on the drawer's Manage tab, three
taps deep and behind a settings form.

The guest drawer header now carries an Open action whenever a URL is
saved, built from the shared WebInterfaceLink and drawer-header action
class, so the service is one tap from the expanded row with a 44px
phone target. Rows are unchanged: showing the row link at 393px
truncated names until checkout-web-01 and checkout-web-02 read the
same.

Refs #1556.
2026-10-04 23:49:55 +01:00
pulse-triage[bot] 5e7e49650f Keep missing drawer History readings out of continuous trends
Split series at actual in-panel stored times without that metric, retaining every isolated and measured-zero observation. Explain gaps visibly and accessibly without guessing cadence, outage cause or duration. Live recovery cannot fill stored gaps. Production drawer browser proof and connected controls preserve source/access isolation, exact-time inspection, independent readings and the request budget.

Change-source: pulse-maintainer
2026-10-04 23:40:10 +01:00