Commit Graph

7365 Commits

Author SHA1 Message Date
rcourtman 77134632e2 Register platform section tabs governance 2026-06-12 14:01:39 +01:00
rcourtman 51ac42870c Canonicalize platform error states 2026-06-12 13:52:53 +01:00
rcourtman 0109361296 Register platform loading template governance 2026-06-12 13:38:05 +01:00
rcourtman f90ae48814 Register FilterBar template governance 2026-06-12 13:29:25 +01:00
rcourtman 1e634709a2 Canonicalize platform table empty states 2026-06-12 13:08:40 +01:00
rcourtman 97bb7035d9 Canonicalize inline detail table rows 2026-06-12 12:55:56 +01:00
rcourtman 43bb0184ba Canonicalize node header web interface links 2026-06-12 12:31:31 +01:00
rcourtman ddb5bd5e1a Canonicalize Proxmox web interface links 2026-06-12 12:22:20 +01:00
rcourtman 2169d94b41 Canonicalize platform row detail toggles 2026-06-12 12:11:50 +01:00
rcourtman b179b1782c Stabilize Proxmox guest Docker inventory 2026-06-12 12:10:54 +01:00
rcourtman d221640086 Retire platform table frame allowlist 2026-06-12 11:36:18 +01:00
rcourtman 6dc931aac6 Migrate host tables onto PlatformTableShell 2026-06-12 11:07:14 +01:00
rcourtman eab067171c Clarify Proxmox Docker LXC host setup 2026-06-12 10:46:38 +01:00
rcourtman 048db1c91c Enforce shared template registry for platform tables 2026-06-12 10:44:11 +01:00
rcourtman 93420753aa Enforce shared templates for web interface links
Refs #1276
2026-06-12 10:06:01 +01:00
rcourtman 841100caa7 Clarify Proxmox LXC Docker install path 2026-06-12 09:57:58 +01:00
rcourtman fa62d7f986 Make Docker state column conditional 2026-06-12 09:31:31 +01:00
rcourtman 25a3755bfc Refine Docker container table signals 2026-06-12 09:27:36 +01:00
rcourtman f6a13341a0 Fix Docker container table overflow 2026-06-12 09:22:21 +01:00
rcourtman 9068f3bc6c history(drawer): restore sub-day ranges to the metrics history select
v5 drawers offered 1h/6h/12h history zoom; the v6 shared range list
started at 24 hours, so an operator investigating a spike from the last
hour got a day-wide chart. The backend chart range parser and the
HistoryTimeRange type already support the shorter windows; only the
shared HISTORY_CHART_RANGES list and the drawer labels were missing
them. StoragePoolDetail keeps its own narrower range list and is
unaffected. Flagged by the Machines page v5 parity audit; applies to
every drawer History tab.
2026-06-11 21:50:20 +01:00
rcourtman db842cb6b5 machines(table): restore v5 at-a-glance signals lost in the rebuild
The Machines page v5 parity audit (same lens as #1471 on the disks
table) found the rebuilt table dropped every threshold-aware signal v5
Hosts had, and the rebuild mislabeled one datum outright.

- Temperature cells render through the canonical TemperatureGauge
  (muted / amber >=70 / red >=80) like the Proxmox nodes table; v6 had
  been rendering plain neutral text, so a hot machine never stood out.
- CPU, memory, and disk bars receive per-resource alert thresholds via
  hostOverrideIdCandidates + alertsActivation, mirroring the
  Infrastructure host table; custom thresholds were ignored here.
- Network interface tooltips show cumulative RX/TX counters as bytes;
  they were formatted as rates (a 16-day total read as 1.46 GB/s).
- Temperature tooltip sections cap at six rows with a visible +N more
  marker instead of silently dropping readings (worst-first sort keeps
  the alarming ones); the section title flattening skips empty values.
- The memory bar passes the agent-reported reclaimable cache through,
  completing the used | cache | free split for machines.
2026-06-11 21:50:01 +01:00
rcourtman ff5a0b4957 memory(cache): extend the reclaimable split to standalone host agents
f62f35e24 restored the v5 used | cache | free memory split for Proxmox
nodes and guests, but standalone host agents still reported a flat
used/free pair, so the Machines page memory bar could not show the
reclaimable segment. Flagged by the Machines page v5 parity audit.

- Host agent reports cacheBytes (gopsutil Available minus Free); the
  ZFS ARC adjustment recomputes free so used + cache + free still
  covers the total.
- ApplyHostReport maps the field into models.Memory.Cache and clamps
  inconsistent or older-agent reports so used + cache never exceeds
  total.
- AgentMemoryMeta carries cache onto unified resources so the frontend
  agent payload exposes it.
- Mock generic hosts split a third of non-used pages as cache, and the
  node-linked host conversion now holds the invariant instead of
  stacking the node's cache on top of a recomputed free.
- Contracts: monitoring, unified-resources, and storage-recovery now
  document the split (also covering the f62f35e24 node/guest surface,
  which landed without contract deltas).
2026-06-11 21:47:30 +01:00
rcourtman d38b8a6d00 Preserve Docker container metadata in unified resources 2026-06-11 21:03:14 +01:00
rcourtman cc684ab689 Highlight Assistant code blocks once the turn settles (OpenCode parity)
OpenCode syntax-highlights code in the transcript; Pulse rendered
fenced blocks as flat text. Assistant answers are full of shell
commands, configs, and query output, so add highlight.js with an
infra grammar set (bash, json, yaml, ini, sql, dockerfile, nginx):

- Lazy-loaded into its own vendor-highlight chunk; the entry chunk
  stays clean and the chunk only downloads with the rest of the
  preloaded app shell.
- Highlighting runs over the sanitized DOM after the turn settles, so
  generated spans never pass through DOMPurify, streaming morphs are
  never fought, and the class allowlist stays closed to the model. The
  single carve-out is marked's fence-language hint, pattern-pinned to
  language-x on <code> only.
- Token palette maps hljs classes to theme colors readable on the
  dark prose-pre background both app themes share.

Per-block copy is intentionally not added: OpenCode has none, and the
answer-level copy already exists.
2026-06-11 20:58:17 +01:00
rcourtman db7679a984 Notify when the Assistant finishes or needs you while the tab is in the background
Assistant turns on slow routes run one to two-and-a-half minutes, and
operators tab away; OpenCode raises a desktop notification when a turn
completes, errors, asks a question, or waits on permission while the
terminal is blurred. Pulse now does the equivalent through the browser
Notification API behind an opt-in bell toggle in the drawer header:
enabling requests permission, a denial keeps the toggle off with
guidance, and the preference persists per browser.

Notifications fire only when the tab is hidden or unfocused, carry
fixed generic copy (OS notification centers persist text, so answers,
commands, and resource names never travel through them), coalesce per
kind, and click-to-focus. Dispatched once per stream event at the top
of processEvent, never inside store updaters.
2026-06-11 20:44:28 +01:00
rcourtman 34363bc181 docker(host-drawer): surface check-updates and update-all actions
v5 had 'Update All (N)' and per-host 'Check updates' one click away in
the Docker filter bar. The v6 rebuild moved those actions into the
generic resource drawer's docker-host section, but no surface in the
current IA mounts that drawer for a docker-host: the Docker page hosts
table uses DockerHostDrawer (counts only) and the Machines page does
not list container hosts. The actions were orphaned; the only way to
update containers was one row at a time.

Wire the existing useResourceDetailDrawerDockerActionsState hook into
the host drawer's Containers card: Check updates, Update all (N) with
confirm-click, queued-command status, and an updates-last-checked row.
Update all hides when the server disables Docker update actions and
disables while a host command is in flight or no updates are known.
2026-06-11 20:39:45 +01:00
rcourtman e12b7507e2 docker(drawer): unlock metrics history and container facts for app-containers
The resource drawer gated its History tab on type === 'agent', hiding
per-container CPU/memory/disk/IO charts even though the metrics store
records them and the charts API maps app-container to the
dockerContainer/docker store keys end-to-end (metrics_targets.go hands
out the raw container ID precisely so those records resolve). v5
charted every container; v6 charted none.

- Gate the History tab on a resolvable metrics target instead of the
  agent type. Resources without a target (images, volumes, networks)
  keep no tab.
- Return current-value fallback metrics for any resource, not just
  agents, so charts have a hint while the store fills.
- Add a Container section to the table-row summary: image, restart
  count (red above the v5 crash-loop threshold), created-at, compose
  project/service, and the full label set. All of it ships in the
  payload; none of it rendered anywhere after the v5 drawer was
  retired.
2026-06-11 20:39:28 +01:00
rcourtman 3c49dd2ee9 docker(tables): restore v5 information to the Docker page tables
The v6 Docker page regressed against its v5 ancestor (same disease as
the physical disks table, #1471): container CPU/memory flowed all the
way into the unified payload but no column rendered them, so a container
pegged at 75% CPU looked identical to an idle one. Labels (compose
project/service, traefik rules) and the Swarm stack name shipped in the
payload but were unrendered and unsearchable.

- Add always-visible CPU and Memory metric-bar columns to the containers
  table (ResponsiveMetricCell / StackedMemoryBar, same primitives as the
  hosts table above it).
- Flag crash-loopers: restart counts above 5 render red, matching the
  v5 threshold.
- Demote the Runtime column to mixed-runtime fleets only; it repeated
  host-level data on every container row (the #1471 Source-badge
  pattern) and the Host column sits right next to it.
- Add a Stack column to the Swarm services table, derived from the
  serialized stack field's canonical com.docker.stack.namespace label.
- Make container/service labels and stack names searchable via the
  Docker search haystack (v5 searched labels).
- Render volume Created as relative time with the absolute timestamp in
  the tooltip instead of a raw ISO 8601 string.
2026-06-11 20:39:05 +01:00
rcourtman 2fcf79f32a Stabilize mock broadcast freshness test
Refs Build and Test failures on pulse/v6-release.
2026-06-11 20:29:13 +01:00
rcourtman 61fce38a71 alerts(history): carry alert metadata to the frontend so resourceType resolves
The alert engine stamps metadata.resourceType on every alert, but the
websocket state path converts alerts.Alert to models.Alert, which had no
Metadata field, so every active alert reached the frontend stripped. The
history Type badge then fell back to unified-store lookups that miss
nodes (alert.resourceId is the platform-native node ID while unified
resources mint canonical ids, and alert.resourceName is the raw node
name while unified resources prefer the display name), rendering
Unknown. In mock mode the generated history rows had the same gap.

models.Alert gained the Metadata field in f62f35e24 (it rode along with
the memory-cache commit); this completes the transport:

- copy Metadata in activeAlertsSnapshot (websocket active alerts),
  GetRecentlyResolved (resolved alerts to state), and the mock
  UpdateAlertSnapshots conversion; sources are deep clones already
- deep-copy Metadata in models cloneAlert to keep the snapshot
  clone contract honest
- stamp resourceType in the mock history generator using the real
  engine vocabulary (node, vm, system-container)
- recognize system-container in the history Type badge map; that is
  what the v6 engine stamps for LXC guests

Verified live in mock mode: history previously resolved 277 of 780
rows to Unknown (all node alerts); now 718/718 rows and 19/19 active
alerts carry resourceType and zero badges render Unknown.
2026-06-11 19:53:45 +01:00
rcourtman 7d1ff3674a memory(cache): hold the used+cache+free invariant as mock metrics drift
The mock random-metrics updater recomputes Used/Free from the sampled
percentage but left Cache at its generation-time value, so a drifting
node could show used+cache > total and a 102% 'Shown in Proxmox' row.
Clamp the cache into the non-used pages in applyMemoryUsage, and teach
the memory-bar presentation to clamp defensively so a momentarily
inconsistent snapshot can never render segments past 100%.
2026-06-11 19:50:44 +01:00
rcourtman f62f35e24d memory: restore the v5 reclaimable-cache split end-to-end
v5 modeled memory as used | cache | free (Memory.Cache, 'reclaimable
buff/cache') and the memory bar's tooltip carried a 'Shown in Proxmox'
row explaining why Pulse's percentage reads lower than the Proxmox UI's
cache-inclusive number — a recurring support question. The v6 rebuild
deleted the field from the backend model, so the split and the
reconciliation vanished product-wide. Flagged by the Proxmox overview
parity audit.

Backend: re-add Memory.Cache; split it out via a shared
splitReclaimableMemory helper at the node resolver (node status reports
truly-free directly) and the VM builder (when guest meminfo reported
free pages); transport as proxmox.memoryCache on unified resources
alongside swap/balloon; mock mode populates plausible cache for nodes
and VMs.

Frontend: cache prop on StackedMemoryBar with the v5 muted-amber
segment between active and balloon, tooltip rows for reclaimable cache,
truly-free Free (balloon-capped), and the 'Shown in Proxmox'
reconciliation; guest and node memory adapters normalize free to
truly-free at the boundary; guest and node drawers grow a Reclaimable
cache row; the Proxmox nodes table passes cache and node swap through.
2026-06-11 19:42:40 +01:00
rcourtman 40058fbc49 Fix storage row boundary guardrail
Refs Build and Test failures on pulse/v6-release.
2026-06-11 19:40:08 +01:00
rcourtman 0b065f2103 storage(pools): demote the Source column, fix PBS host labels, tighten rows to 32px
Three more findings from the v5-parity audit of the pools table, all
mirroring the disks-table fixes in 809e2c900:

- The Source column rendered an identical PVE badge on every row: the
  surface's only live embedding (the Proxmox page) forces
  source=proxmox-pve, so the column was pure noise. Drop it and surface
  the record's platform as a Source row in the expansion's Configuration
  card; the FilterBar source chip still covers unforced embeddings, and
  persisted sort-by-source selections fold back to priority.
- PVE-configured PBS storage rows showed raw agent/storage resource ids
  in the Host column because the backup-repository host-label chain
  skipped the owning node and fell through to parentId. Name-bearing
  candidates (parent name, PBS instance name, PVE node, storage nodes)
  now come before raw ids.
- Rows tightened from 38px to the 32px platform table standard, and the
  freed Source width redistributed across Storage/State/Type/Host/Usage.

Also drops the row model's never-rendered platformLabel/platformToneClass/
compactImpact/compactIssue/compactIssueSummary fields - the same
extracted-but-unrendered drift flagged in #1471.

Verified live in mock mode: headers Storage/State/Type/Host/Protection/
Usage/Growth, backup-vault hosts show pve1-3, expansion shows Source PVE,
rows measure 32px.
2026-06-11 19:25:43 +01:00
rcourtman 2547255aea workloads(columns): let explicit toggles override responsive column gating
Below the 1440px wide breakpoint the workloads table layout-gated IP,
Node, Tags, OS, Net I/O, and Disk I/O with no recourse: the columns
menu still offered the toggles but flipping them changed nothing, and
v5's horizontal-scroll fallback was gone, so a 1280px laptop simply
could not see guest tags or I/O rates. Flagged by the Proxmox overview
parity audit; applies to every workloads surface.

- Track explicitly pinned columns per storage scope
  (workloadsHiddenColumns[:scope]:forced); pinned columns render even
  when the breakpoint gates them off and the table wrapper's existing
  overflow-x-auto picks up the slack.
- Menu checkboxes now mirror what the table actually renders: a
  layout-gated column reads as hidden, the first toggle pins it, the
  second unpins it back to the layout default (without poisoning the
  user-hidden set for wide viewports).
- Column reset clears pinned columns along with hidden ones.
2026-06-11 19:16:57 +01:00
rcourtman 955c750019 kubernetes(overview): restore v5 healthy/total fractions on cluster counts
The v5 clusters table read "2/3 ready" the moment a node went down;
the v6 rewrite flattened Nodes / Pods / Deployments to raw totals, so a
cluster with a NotReady node still showed a plain "3". Roll attention
counts (danger/warning indicator variants) into the per-cluster child
rollup and render healthy/total with the amber tone on shortfall.
Muted states (Unknown, deployments scaled to zero) deliberately do not
count as attention.
2026-06-11 19:14:07 +01:00
rcourtman a24e90f7c6 proxmox(overview): label the workloads identifier column ID, not Info
On a pure-Proxmox surface the polymorphic 'info' column always renders
the VMID; the generic 'Info' header was unified-model vocabulary leaking
into a platform page (v5 called it 'ID'). Use the existing
columnLabelOverrides hook. Flagged by the Proxmox overview parity audit.
2026-06-11 19:11:10 +01:00
rcourtman f0c5b38a3f kubernetes(tables): shared scope labels, humanized event times, drop Observed
Noise findings from the v5-parity audit:

- Eight tables carried copy-pasted scope helpers that preferred the
  internal cluster id over the display name, so the pods table read
  "k8s-production-1/services" while the deployments table said
  "Production EU" for the same cluster. Replace every local helper
  with shared kubernetesScopeLabel / kubernetesClusterLabel (name
  first, id fallback) in the page model.
- The events Observed column rendered the raw ISO timestamp with
  microseconds; humanize it like every other age on the page and keep
  the raw timestamp on the hover title.
- Drop the Deployments Observed column: raw observedGeneration with no
  spec generation beside it fails the what-does-the-user-do test, the
  same shape as the Source badge removed from the disks table in
  #1471. The value still ships in the resource payload.
2026-06-11 19:08:45 +01:00
rcourtman 5dfaf96cae proxmox(overview): render the guest totals the page model already computes
v5's dashboard closed with a 'N running | N degraded | N stopped' strip
(Dashboard.tsx); the v6 rebuild kept computing those counts in
ProxmoxPageSummary — tests and all — but nothing ever rendered them,
the same flows-but-never-renders failure mode as #1471's wearout field.
Add a degradedGuestCount to the summary and close the overview with a
compact totals strip using the status-chip vocabulary (running /
attention / stopped). Flagged by the Proxmox overview parity audit.
2026-06-11 19:08:17 +01:00
rcourtman 91f601317f proxmox(backups): surface PBS host CPU, memory, and uptime
v5 listed PBS instances in the dashboard nodes table with live CPU,
memory, and uptime; the v6 IA moved PBS to the Backups tab but its
server table carried only Status/Version/Datastore columns, leaving PBS
host health invisible on the entire Proxmox surface. The unified pbs
resource already ships the metrics, so add CPU, Memory (with byte
sublabel), and Uptime columns to the backup-servers table; they ride on
each of the server's datastore rows. Flagged by the Proxmox overview
parity audit.
2026-06-11 19:03:04 +01:00
rcourtman 4e79a991a3 kubernetes(drawer): surface QoS, containers, node identity, and API server
The v5-parity audit found data reaching the browser and rendering
nowhere, same disease as the #1471 disks table:

- Per-container status (state, reason, readiness, restarts, image) was
  only ever aggregated into x/y ready counts; v5 listed each container.
- Node OS image / kernel / architecture and the capacity vs allocatable
  budget (cores, memory, pods) had zero consumers. For nodes without a
  linked Pulse host agent the generic host section reads agent data
  only, so the OS identity surfaced nowhere at all.
- The cluster API server endpoint was searchable but rendered nowhere;
  pendingUninstall reached the browser and no surface used it.

Add a Kubernetes section to the resource detail drawer (pods: QoS +
per-container rows; nodes: OS identity + capacity/allocatable +
cordon state; clusters: API server / context / agent version / pending
uninstall), reusing the platform-neutral section shape introduced for
TrueNAS. Show pod capacity in the nodes table Capacity column and badge
pending-uninstall clusters in the clusters table.
2026-06-11 19:00:20 +01:00
rcourtman 9f52cf2a2d kubernetes(adapter): project pod QoS class into the unified resource
The agent collects QoSClass and the legacy models carry it, but
resourceFromKubernetesPod dropped it, so no v6 surface could render
what v5's pod expansion showed. Additive optional field; proof added
to the kubernetes registry ingest test.

Found by the v5-parity audit of the Kubernetes page.
2026-06-11 18:57:38 +01:00
rcourtman 3ec3a055ca proxmox(overview): make the nodes table sortable again
Every v5 NodeSummaryTable column header sorted; the v6 rebuild shipped
static headers. Restore click-to-sort on all columns except the Web
link: identity/text columns default ascending, metric columns default
descending, repeat clicks flip then clear back to the snapshot order,
and missing values (offline metrics, absent temperatures) stay last in
either direction. Flagged by the Proxmox overview parity audit.
2026-06-11 18:57:22 +01:00
rcourtman 37a22009b7 proxmox(overview): restore v5 node-row signals
The v6 nodes table dropped four glanceable signals its v5 ancestor
(NodeSummaryTable) carried: alerting rows lost their red/yellow accent
and background tint, pending apt updates lost the escalating badge in
the name cell, uptime was rounded to whole days, and offline nodes
rendered at full opacity. Flagged by the Proxmox overview parity audit.

- Wire getAlertStyles over the websocket alert store (alerts key on the
  canonical unified resource id) and reuse the workload-row accent CSS
  through a host-row variant.
- Pending-updates badge (orange at >=10, yellow below) beside the name.
- Full '26d 4h' uptime via the shared formatUptime; <1h warning kept.
- opacity-60 on offline rows, matching guest-row stopped dimming.
2026-06-11 18:51:10 +01:00
rcourtman 61d75dfded proxmox(overview): restore the node web-interface link end-to-end
The v6 unified-resource node shim never read the PVE URLs the backend
ships (proxmox.guestUrl / proxmox.host): guestURL came from a
nonexistent customURL field and host carried the bare node name, so the
workloads group-header link resolved relative to the SPA
(/proxmox/pve1 in a new tab) and the nodes table had no link at all.
v5's dedicated link column (NodeSummaryTable) was silently dropped in
the rebuild. Flagged by the Proxmox overview parity audit.

- Map proxmox.guestUrl into guestURL with the proxmox.host API URL as
  fallback in nodeFromResource; host stays a hostname label because the
  node drawer renders it as one.
- Add getNodeExternalUrl: only URL-shaped values become link targets,
  bare hostnames fall through to the canonical https://<node>:8006.
- Point NodeGroupHeader at the helper.
- Add a Web column (Machines-table pattern) to the Proxmox nodes table
  at compact and wide layouts.
2026-06-11 18:43:26 +01:00
rcourtman 5ba53f681d alerts(history): restore v5 information density to the alerts surface
The v6 alerts history regressed against its v5 ancestor (same audit
pattern as the physical-disks table in 809e2c900, flagged from the
alerts-surface parity audit):

- The Type column silently swapped which-alert-fired (cpu, backup,
  powered-off) for the unified resource kind (VM, LXC); the humanized
  alert-type label was computed into HistoryItem.title and never
  rendered. Render title in the Type column, colored by resource kind
  with the kind as hover title - the exact v5 scheme.
- The #1218 stable id tiebreaker was dropped from both the overview
  and history sorts, so alerts fired in the same polling cycle scramble
  on re-render. Restore it in useAlertOverviewState and
  applyAlertHistoryWindow, with regression tests.
- Duration, Status, and Node were hidden below lg with no recovery
  path; v5 kept all columns reachable via horizontal scroll. Un-hide
  them and give the table the v5 min-w-[800px] inside the shared
  scroll wrapper.
- Drop the Source column: buildAlertHistoryItems only ever emits
  source 'alert', so the badge was identical on every row (the 'ai'
  Patrol merge never happens). Remove the dead presentation helper;
  the source field stays on the model for the action-button guards.
- Treat LXC like CT/Container in the resource-kind badge palette
  (live mock showed LXC rows falling to the gray bucket).
2026-06-11 18:43:20 +01:00
rcourtman a61b44f46c kubernetes(status): make degraded k8s states visible and filterable
The canonical Kubernetes adapter reports every unhealthy-but-alive state
(cordoned node, not-ready container, pending pod, partially available
controller) as `warning`, but the platform page's filter triad only
accepted degraded/paused, so those rows matched none of the Healthy /
Degraded / Offline chips and were findable only under All. Flagged by
the v5-parity audit: v5's Unhealthy filter classified all of these.

- Accept `warning` into the page's degraded filter bucket.
- Declare `unschedulable` on ResourceKubernetesMeta (the adapter has
  projected it all along) and map ready-but-cordoned nodes to an amber
  Unschedulable indicator instead of green Ready. NotReady still wins.
- Render the mapped status label in the pods table status column. The
  raw phase reads Running while a container is in CrashLoopBackOff;
  the reason was previously visible only as the dot's hover title.
- Add `warning` to the frontend ResourceStatus union to match what the
  backend emits.
2026-06-11 18:40:07 +01:00
rcourtman 8ce0f4a872 Show context-window usage in the last-turn summary (OpenCode parity)
OpenCode shows how full the model's context is next to the composer;
Pulse showed raw token counts with no sense of scale. The done event
now carries context_limit_tokens (providers.ContextWindowTokens for
the selected route) and the drawer's last-turn summary renders input
tokens as a percentage of the window: '8,500 tokens (6% of context)',
with the full breakdown in the hover detail. Without the limit the
summary renders exactly as before. Pinned in the plain-answer corpus
scenario.
2026-06-11 17:18:42 +01:00
rcourtman e6250fe697 storage(pools): collapse duplicate Ceph rows and lift cluster health onto the mount
Every Ceph-backed storage appeared twice on the pools table: once as the
PVE storage entry (online, mounted capacity) and once as the
cluster-internal pool row synthesized from Ceph telemetry (degraded, raw
pool accounting) - same name, conflicting state and usage, no visual
relationship. Found by the v5-parity audit; v5 rendered one row with a
cluster-health badge.

consolidateCephClusterPoolRecords now collapses the synthesized pool
rows (type ceph on the cluster pseudo-node) into the PVE rows that
mount them, matched by backing-pool detail or name. The pool row's
worse health is lifted onto the survivor (state cell, status detail,
issue summary) so a degraded cluster stays visible on the table, and
unmounted pool rows are kept so standalone-monitored clusters do not
lose their only capacity row. Raw per-pool accounting stays on the
Ceph tab's cluster drawer. Registry synthesis and pool alerting are
untouched.

Also pins the zfsSummary devices field in useStoragePoolDetailModel's
test, missed by b53e1a3e0.
2026-06-11 17:14:43 +01:00
rcourtman 07e4501d08 Stream forming tool args into the pending tool row
While the model writes tool arguments the pending row showed only a
generic placeholder plus 'Receiving tool input.' for what can be tens
of seconds on slow routes. OpenCode streams the forming call into the
mutable tool part; Pulse now shows the live tail of the raw args in
the pending row whenever they are still unparsable, and drops it the
moment the input parses into a real summary or command preview. The
raw input was already carried by tool_progress events; this only
renders it.
2026-06-11 17:14:43 +01:00