Commit Graph

7356 Commits

Author SHA1 Message Date
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
rcourtman e1cd15d762 Fix storage pool detail ZFS summary test
Refs Build and Test failures on pulse/v6-release.
2026-06-11 17:06:28 +01:00
rcourtman da10d9a5dd Mark the post-tool status handoff so silent reasoning never shows a stale tool status
After a tool round, the next model turn can reason server-side for tens
of seconds without streaming a single event. The footer kept the last
tool's status counting up ('Reading inventory. (40s)') while the model
was already composing the answer — observed live on the OpenRouter
reasoning route. OpenCode always shows a thinking state here; Pulse now
emits a model_processing workflow state at the start of every post-tool
turn ('Working on the response with the gathered results.'), which
model_thinking upgrades when reasoning deltas actually arrive.

Pinned by a new interaction-quality corpus scenario.
2026-06-11 16:52:02 +01:00
rcourtman b53e1a3e0e storage(pools): carry the full ZFS pool payload to the storage surface
The unified-resource adapter flattened models.ZFSPool to four scalars
(state + R/W/C totals), so scan status and the per-device report never
reached the frontend. The storage detail's ZFS card, the usage bar's
scrub/resilver overlay and ZFS tooltip, and ZFS-device-to-disk matching
were all dead code on the unified path: a degraded pool rendered as a
one-word State cell with no way to see which device failed or that a
resilver was running. Found by the v5-parity audit of the pools table
(same pipeline disease as #1471 on the disks table).

- Add StorageMeta.ZFSPool and populate it in resourceFromStorage
  (normalized copy, not aliased).
- Serialize it in monitorStoragePlatformData so the /api/state path
  carries it alongside the existing flat scalars.
- Map it through normalizeStorageMeta and the storage record adapter
  (meta first, flat platformData fallback).
- Render the per-device report (name, vdev type, state, R/W/C errors,
  message) in the detail's ZFS Pool card - the v6 home for what v5
  showed via the health-map dots and the degraded-pool warning row.
- Pin the end-to-end path in code_standards_test and update the
  unified-resources, monitoring, storage-recovery, and
  frontend-primitives contracts.

Verified live in mock mode: degraded pool now shows the resilver scan,
pool error totals, per-device states, and the bar pulse overlay.
2026-06-11 16:48:57 +01:00
rcourtman cbb3b42ce0 Cancel superseded Build and Test runs
Refs CI notification noise on pulse/v6-release.
2026-06-11 16:32:09 +01:00
rcourtman e1d8bfbb06 Gate the Proxmox Replication tab on fetched jobs and restore Next sync
The tab was gated on replicationChanges, recentChanges keyword-matched
for replication/replica. No production path emits such a change: change
emission produces generic reasons, ChangeKind has no replication value,
and no replication alerts exist. So when 2315d8330 removed the
hasPveEstate clause the tab became unreachable on every estate,
including ones with active pvesr jobs (rc.6 shipped this way). The
backend was intact throughout: monitor_pve_replication.go collects and
/api/replication/jobs serves; only the gate was dead. The vitest that
covered the gate passed by fabricating a change reason ("Replication
job completed") that nothing in internal/ generates.

Replication deliberately bypasses the unified-resource pipeline, so the
tab is now gated on its canonical source instead: ProxmoxPageSurface
owns the jobs fetch, shows the tab when jobs exist, and feeds the same
data to a now-presentational ProxmoxReplicationTable.

Also restore v5's Next sync column (countdown with overdue/imminent
tones), which the v6 rebuild dropped while the payload kept carrying
nextSyncTime/nextSyncUnix, and type the *Time fields as the RFC3339
strings the Go model actually serializes. The page model keyword
machinery and its fabricated-change test are removed; tab visibility
tests pass the job count explicitly.
2026-06-11 16:30:58 +01:00
rcourtman 7e26592f11 Fix CI SMART and grant refresh tests
Refs Build and Test failures on pulse/v6-release.
2026-06-11 16:26:21 +01:00
rcourtman 70a20f6ac7 Generate session titles from the first exchange (OpenCode parity)
Session titles were the truncated first prompt, so the picker filled
with 'Reply with exactly: OK' and five identical 'hows esphome' rows.
OpenCode background-generates a short title after the first exchange;
Pulse now does the equivalent: once the done event has reached the
client, the chat runtime asks the configured chat model for a <=50-char
title from the first user message and reply, then renames the session.

- Runs only on the first exchange; any customized title is left alone,
  with a re-check before persisting to lose races against a concurrent
  rename. Failure or timeout keeps the placeholder.
- Title requests pass the model-boundary sanitizer like normal turns
  and record usage as assistant_session_title.
- Synchronous after done (no user-visible latency, no goroutine racing
  service teardown), bounded at 20s for slow reasoning routes.

Verified by tests end-to-end (hook through Rename); live verification
on a real provider route pending while the dev backend sits in mock
assistant mode, which intercepts turns before the agentic path.
2026-06-11 16:21:58 +01:00
rcourtman d26f9da709 Flag pre-rc.6 SSH sensor setups and surface a re-run-setup notice
Nodes that set up SSH temperature monitoring before v6.0.0-rc.6 have an
authorized_keys entry locked to `sensors -j`, so the pulse-sensors wrapper
never runs and SMART (SATA/SAS) disk temperatures silently never arrive;
only NVMe shows via kernel hwmon (what the #1471 reporters hit). The server
already detected the legacy payload but only logged it.

- models.Temperature gains LegacySensorsFormat, set by parseSensorsJSON
  when the payload is raw sensors -j, and preserved across the host-agent
  merge; it rides the existing temperatureDetails projection on unified
  node resources.
- New PlatformOutdatedSensorSetupNotice on the Proxmox page, data-gated:
  collection succeeded AND payload was legacy AND the node has SATA/SAS
  disks without temperatures, so nodes whose disk temps arrive via a host
  agent stay clean. Copy points at re-running the node setup script, which
  replaces the locked key entry and installs the wrapper.
- monitoring.md contract extended with the legacy-format marker behavior.
2026-06-11 16:00:25 +01:00
rcourtman 809e2c9008 storage(disks): restore v5 information density to the physical disks table
The v6 physical disks table regressed against its v5 ancestor: device
path was demoted to a tooltip (identical models became indistinguishable),
SSD wearout vanished from the surface entirely despite flowing all the way
into the presentation layer, the Belongs column rendered empty for every
disk without a ZFS pool mapping, and a Source badge spent a column saying
PVE on every row. Flagged from the side-by-side screenshots in #1471.

- Add a Device column (mono devPath, md+).
- Add a Life column rendering wearout with v5-style thresholds (md+).
- Fall back Belongs to Proxmox's usage string (ZFS / ext4 / BIOS boot)
  when no pool mapping exists; searchable via the existing haystack.
- Demote the Source badge to the row expansion header next to the
  serial; drop the table column.
- Render missing temperatures as muted dashes instead of green ones
  (getTemperatureTextClass(0) returned the healthy tone).
- Tighten rows from 38px to 32px to match the platform table standard.
2026-06-11 15:58:58 +01:00