Commit Graph

6360 Commits

Author SHA1 Message Date
rcourtman a191f2ab2b monitoring: surface CrashLoopBackOff pods as degraded on the frontend
monitorFrontendStatus's "pod" branch was re-deriving status from
Kubernetes.PodPhase alone, so a CrashLoopBackOff pod (Phase=Running
with unready containers) was reported as "running" even though
statusFromKubernetesPod had already classified it as StatusWarning.

Drop the unconditional phase=running -> "running" mapping and fall
through to the unified-status switch, which surfaces StatusWarning as
"degraded". Phase-based mappings for pending/unknown/succeeded/failed
are kept since they still cover edge cases where the unified status
is StatusOnline but phase carries useful information.

Adds two regression cases in TestMonitorFrontendAndMetricHelpers:
- pod warning running degraded (CrashLoopBackOff)
- pod online running running (healthy baseline)
2026-05-19 22:37:55 +01:00
rcourtman 8ae422c243 Filter upstream tables on platform overviews
The workload-filter search at the top of each platform overview page now
also narrows the Hosts / Nodes / Clusters tables above the workloads
table, mirroring how Docker filters its hosts table from the same input.
Typing "pve1" on the Proxmox overview now reduces the Nodes table to
matching rows; typing a cluster or namespace fragment on Kubernetes
narrows clusters/nodes/deployments. Status / view / runtime filters
remain scoped to the workloads table (they don't apply to nodes).

Adds a shared `resourceMatchesSearch` helper in src/utils that walks the
common Resource identity fields (name, displayName, hostnames, cluster
and namespace names, tags, canonical aliases).
2026-05-19 22:18:29 +01:00
rcourtman cd85d581a3 Add platform table resource drawers 2026-05-19 22:05:45 +01:00
rcourtman 11ab890880 mock: extend offline/degraded posture to Docker, PBS, PMG, and host agents
Round out the curated demo estate so every platform page surfaces a
non-healthy entry rather than rendering uniformly green:

- generator.go: always seed a secondary PBS and PMG instance (drop the
  random spawn gates) so the demo PBS and PMG pages reliably show two
  instances.
- demo_scenarios.go (applyDemoDockerScenario): force docker host index 2
  to "field-office-edge-01" with status=offline and exited containers,
  giving the Docker page a disconnected host with stopped workloads.
- demo_scenarios.go (applyDemoKubernetesScenario): mark
  prod-euw1-k8s-03 NotReady in the last cluster so the k8s nodes
  table shows a NotReady worker.
- demo_scenarios.go (applyDemoHostScenario): also force the
  prod-euw1-k8s-03 host agent offline so the Hosts page shows an
  outage that matches the k8s nodes view.
- demo_scenarios.go (applyDemoStorageScenario): mark the dr-vault PBS
  and mail-gateway-us PMG instances as degraded with degraded
  connection health.
- demo_scenarios_test.go: update the curated-posture helpers to expect
  the new offline/degraded entries while still asserting the rest of
  the estate stays healthy after metric refreshes.
2026-05-19 22:03:06 +01:00
rcourtman 58b2e10c1f Hoist workload filter to top on platform overview pages
Move the WorkloadsFilter to the top of the Proxmox, Kubernetes, TrueNAS,
and vSphere overview surfaces so every workload page renders the filter
bar above its tables, matching the Docker pattern. The mid-page filter
placement was inconsistent across pages and pushed the search input
below the fold on busy hosts/nodes/clusters tables.

Each page lifts useWorkloadsState to its overview component, renders
WorkloadsFilter directly, and passes the state into WorkloadsSurface
(removing showFilterToolbar and the filter* configuration props from
the surface). Filter scope is unchanged — it still governs the embedded
workloads table only.
2026-05-19 21:45:40 +01:00
rcourtman a13bf7da6d Keep guest drawer discovery reads non-suspending 2026-05-19 20:59:34 +01:00
rcourtman 423beeb903 Fix Docker overview drawer interactions 2026-05-19 20:08:58 +01:00
rcourtman cd2be53725 mock: simulate offline Proxmox VE host pve5 and stopped workloads
Configure pve5 as offline, cascading to stopped status on all its guests and zeroing metrics to simulate a node outage. Also force standalone offline states on postgres-replica-01 and dev-portal-01 workloads. Update test helpers and platforms coverage checklist in platform_support_contract_test.go.
2026-05-19 19:51:23 +01:00
rcourtman 02791eaeb0 Add inline drawer route regression tests 2026-05-19 19:46:54 +01:00
rcourtman 86ac84f68e Keep inline drawer expansion local 2026-05-19 19:36:22 +01:00
rcourtman 34d53b23ea Scope platform filters to page context 2026-05-19 18:51:59 +01:00
rcourtman 2d62ae5f19 Make platform filters page-specific 2026-05-19 18:00:43 +01:00
rcourtman 743d4fb95e Clean up dead admitted/first-lab-ready branches after vSphere promotion
PlatformGovernanceState narrowed to 'supported' | 'presentation-only'
once vSphere left the admitted set, so the badge-label helper's
'admitted' branch and the dropped governanceState='admitted' summary
call are unreachable. Hardcode currentAdmissionPath to [] and drop the
unreachable 'admitted'/'first-lab-ready' branches; the function still
accepts the wider type so it stays valid if a future platform is
re-admitted.
2026-05-19 17:33:06 +01:00
rcourtman 7652e2833e Promote vSphere from admitted to supported
Flip vmware-vsphere from governance_state=admitted, readiness_stage=
first-lab-ready to supported on both axes. The phase-1 floor (vCenter
client, canonical agent/vm/storage projection, alerts integration,
Assistant read paths, mock fixtures, page surface, automated proof for
the read-only boundary) has been in place; the only remaining gate was
a live vCenter run, and we are taking the platform claim on the strength
of the implementation rather than blocking on that proof.

Add vmware-vsphere to default_infrastructure_source_order, to the
First-class platforms list and Current Support Matrix in
PLATFORM_SUPPORT_MODEL, and to the Pulse primary navigation
(automatically via SUPPORTED_PLATFORM_IDS in the regenerated frontend
manifest). Rename and invert the Go contract test that asserted vSphere
stays admitted, and let the admitted-platform helpers tolerate an empty
set now that nothing currently lives there. Drop the "in first-lab-
ready readiness" line from the vSphere empty state.

PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT used: this commit only flips two
JSON values for one platform; no subsystem contract schema or surface
changed.
2026-05-19 17:27:06 +01:00
rcourtman 073634bcef Refine platform workload filters on Docker pages 2026-05-19 17:14:39 +01:00
rcourtman dadca0f1cc Hide non-native disk columns on Kubernetes workloads 2026-05-19 17:05:08 +01:00
rcourtman e52476f35e Lock Kubernetes workloads page to pod-native columns 2026-05-19 17:02:03 +01:00
rcourtman 97067b6447 Align TrueNAS page with Proxmox/Docker layout
Drop the duplicate Disks and Apps tabs that just remounted tables
already rendered in the Overview stack. Rename the default tab from
"Systems" to "Overview" so the section label matches the stacked
content (systems + pools + disks + apps). The Workloads filter inside
Overview owns app search/grouping, and the Storage tab continues to
provide the full StorageSurface (pools + disks toggle, with toolbar)
for richer storage exploration.

Also dress the TrueNAS systems table CPU/Memory columns with the
canonical bar primitives (ResponsiveMetricCell + StackedMemoryBar) so
the Overview stack reads as one consistent surface alongside the
Docker / Proxmox / vSphere / Kubernetes host tables.
2026-05-19 16:19:34 +01:00
rcourtman aa1f07bd94 Left-align workload image column 2026-05-19 16:16:59 +01:00
rcourtman 2cd4d095f1 Align Kubernetes page with Proxmox/Docker layout
Drop the duplicate Nodes / Pods / Deployments tabs that just remounted
the same tables already rendered in the Overview stack. Rename the
default tab from "Clusters" to "Overview" so the section label matches
what the tab actually shows (clusters + nodes + pods + deployments).
The Workloads filter inside Overview owns pod search/grouping; the
standalone tabs added zero navigation value.

Also dress the Kubernetes nodes table CPU/Memory columns with the
canonical bar primitives (ResponsiveMetricCell + StackedMemoryBar) so
the Overview stack reads as one consistent surface alongside the
Docker / Proxmox / vSphere host tables.
2026-05-19 16:04:06 +01:00
rcourtman 2f464146cb Align vSphere page with Proxmox/Docker layout
Drop the duplicate Virtual Machines tab that just remounted the embedded
Workloads table from Overview. Rename the default tab to Overview and
swap the Storage tab from the bespoke VsphereDatastoresTable to the
canonical StorageSurface (forcedSourceFilter=vmware-vsphere,
forcedView=pools), matching how Proxmox handles its Storage tab and
avoiding the Pools/Physical-disks toggle that previously stranded
operators on an empty-disks state.

Also dress the vSphere hosts table CPU/Memory columns with the canonical
bar primitives (ResponsiveMetricCell + StackedMemoryBar) so the Overview
stack reads as one consistent surface alongside the Docker and Proxmox
host tables.
2026-05-19 15:55:14 +01:00
rcourtman 4d11449cd8 Compact container image names 2026-05-19 15:41:34 +01:00
rcourtman 00b98cf08e Canonicalize runtime badge tones 2026-05-19 15:33:08 +01:00
rcourtman 4156c66059 Clarify Docker workload host identity 2026-05-19 15:18:39 +01:00
rcourtman 882b00652c Add Docker app-container identity coverage 2026-05-19 15:00:14 +01:00
rcourtman 3d500b5af2 Scope app-container registry keys to their docker host
resourceFromDockerContainer only encoded the container name in the
canonical identity, and ingestDockerContainer passed the raw docker
container ID as the source key. Both chooseNewID and the bySource
mapping then hashed only source + sourceID, so any path that briefly
produced the same source ID for two containers across different
docker hosts (a docker ps that returns an empty ID and falls back to
the container name in parseDockerInventoryContainerLine, a future
short-ID truncation, daemon-side identifier reset across recreate
cycles) collapsed both containers into one registry resource. The
second ingest hit mergeInto and overwrote the surviving Docker
payload — including HostSourceID and ParentID — with the colliding
host's data, surfacing on the Docker page as the "frigate@141" host
re-attribution flicker where a container briefly appears under the
wrong docker host before snapping back.

ingestDockerContainer now keys bySource[SourceDocker] by
host.ID/container/ct.ID via dockerContainerSourceID, and
seedSourceIDForResourceLocked produces the same shape for records-
path ingests so both paths resolve to the same resources. The
hashed candidate ID derived from this composite key gives each
(host, container) pair a unique registry entry.

BuildMetricsTarget for ResourceTypeAppContainer prefers
resource.Docker.ContainerID over the bySource SourceID so the
metrics target the API hands out continues to match what the
metrics store writes for container metrics (which still key by
the raw docker container ID).

Add a regression test that ingests two docker hosts each reporting
a container with the same colliding source ID and verifies both
survive as distinct resources with their original HostSourceIDs.
The test fails on the prior implementation (one resource instead
of two).
2026-05-19 14:47:31 +01:00
rcourtman 926906b134 Don't overwrite populated Docker hosts with empty inventory polls
The Proxmox-side LXC Docker inventory poll occasionally produces an
ok=true report with zero CONTAINER lines but a valid PS_OK marker:
docker ps -a inside the LXC genuinely returned an empty list for
that one call, typically while watchtower is briefly recreating a
container or the dockerd is mid-restart. ApplyDockerReport then
wipes the host's container list, every connected WebSocket client
sees every row for that host flash blank, and the next successful
poll 10 s later restores it.

Before calling ApplyDockerReport, check whether the host already
has containers tracked in state. If yes and the new report has
zero containers, log at debug and skip the apply, counting the
poll as skipped rather than collected. A host that is genuinely
empty still applies on first sight (no previous containers means
no previous state to protect).

Add regression tests for both branches: a populated host receiving
an empty report keeps its container list; a freshly-seen empty
host still gets created.
2026-05-19 12:04:51 +01:00
rcourtman 6ce593f474 Skip monitor reload on no-op auto-register
Every ~5 minutes the Pulse agent on a node already covered by an
existing cluster endpoint re-announces itself via POST
/api/auto-register. The handler ran the full add-the-standalone +
consolidate path, which produced an identical persisted-config
state. But it still wrote nodes.enc and called reloadFunc, which
stops the multi-tenant monitor, sleeps 1 s, and rebuilds it from
scratch. During the rebuild the WebSocket-visible resource store
goes empty (nodes:0, vms:0, containers:0, dockerHosts:0), so every
connected client sees every row flash blank and reappear. The
Docker page made the flicker obvious because the rows are dense
and stacked, but the regression hit every page in the UI.

Fingerprint the persisted-config payload before any mutation and
again after consolidation; when the fingerprints match, the
auto-register is a no-op and SaveNodesConfig + reloadFunc are both
skipped. Real changes (a new node, a rotated token, a captured
fingerprint) still flow through unchanged.

Add two regression tests: one drives the warm-up + steady-state
heartbeat pair and asserts the second call does not call
reloadFunc; the other rotates a token and asserts reloadFunc still
fires.
2026-05-19 11:09:14 +01:00
rcourtman 882f4f05b5 Stop treating workloads grouping mode as a filter
Switching to List view used to light up the Reset/Clear all button
even though no chip rendered for it, conflating a view-option
preference with active filter state. Grouped/List is now treated like
the other view options (bars/trends, charts toggle, columns): no
contribution to Clear all visibility, no reset by Clear all.
2026-05-19 10:54:13 +01:00
rcourtman 4ae690a086 Drop VMID from LXC Docker host badge label
Match the canonical LXC badge that appears throughout the workloads
table and other Pulse surfaces: just "LXC", no trailing VMID. The
VMID stays available via the badge tooltip and the host drawer for
operators who need it, but the at-a-glance scan signal is the
hostname column, not a duplicated identifier in the System pill.
2026-05-19 10:24:47 +01:00
rcourtman 474ba326b8 Use canonical green color for LXC Docker host badge
The LXC Docker host badge was hand-coded as amber, but amber is the
canonical color for Pods (workloadTypePresentation.ts). The
canonical LXC color is green, matching the system-container
workload badge that appears throughout the rest of the UI. Import
the workload presentation class rather than hardcoding the green
tokens so the badge tracks the single source of truth.
2026-05-19 10:21:38 +01:00
rcourtman 56d5ffca0a Surface LXC Docker hosts with real metrics and an LXC badge
The Proxmox-LXC Docker hosts row was blank (no CPU / MEM-used /
Disk) and the display name appended a noisy "(LXC <vmid>)" suffix,
because the inventory script only collects Docker daemon metadata,
not host resource usage. Pulse already polls the same data for the
underlying LXC via the PVE cluster/resources endpoint, so the
Docker host row should mirror what the rest of Pulse already knows.

Before applying each LXC inventory report, enrich the Host info
with CPU / Memory / Disk / Uptime taken from the source LXC's
existing models.Container. Drop the "(LXC <vmid>)" suffix from
the synthesized display name. On the frontend, detect the
proxmox-lxc-docker: hostSourceId prefix and emit a styled
"LXC <vmid>" badge into the System column instead of the
filtered-out runtime-only "docker" badge.
2026-05-19 10:13:39 +01:00
rcourtman fc72e3d8d1 Fix LXC Docker container flicker from partial inventory polls
The Proxmox-side LXC Docker inventory script masked `docker ps`
failures with `|| true`, so when `pct exec` was truncated, slow, or
`docker ps` itself transiently failed, the parser produced an
ok=true report with `containers=[]` and `ApplyDockerReport` blanked
the host's container list. Containers then reappeared on the next
successful poll, producing the visible flicker on the Docker page.

Emit a `PS_OK` marker only when `docker ps` exits cleanly, and
refuse to apply the report when the marker is absent. A genuine
"host has zero containers" state still applies (PS_OK present,
zero CONTAINER lines), but a truncated or failed poll now skips
apply and lets the previous container list survive the inter-poll
gap. Add regression tests for both paths.
2026-05-19 10:00:26 +01:00
rcourtman 1f66a2f93f Collapse Docker page to single unified surface
The "Hosts" tab already rendered the hosts table, the containers
WorkloadsSurface, and the Swarm services table together, so the
separate Containers and Swarm services sub-tabs were strict subset
views of overview. Other platform pages (Proxmox, TrueNAS,
Kubernetes) use sub-tabs for genuinely different data per tab; the
Docker shape was the odd one out.

Drop PlatformSectionTabs from DockerPageSurface and render the
unified hosts + containers + services stack unconditionally. Remove
the now-dead DOCKER_TAB_SPECS / DockerPageTabId / DockerTabSpec /
buildVisibleDockerTabSpecs exports from dockerPageModel. The
DockerPage no longer interprets the URL segment, so legacy
/docker/overview, /docker/containers, /docker/services bookmarks
continue to resolve via the existing /docker/* wildcard route in
App.tsx.
2026-05-19 09:52:10 +01:00
rcourtman 90482607da Bind Proxmox install command tokens on first use 2026-05-19 09:47:05 +01:00
rcourtman 004d0032ee Fix Disconnected badge label overflow
The disconnected branch of ConnectionStatusBadge used the connected
state's min-w-6 h-6 group-hover:px-3 sizing, designed for a circle dot
that expands on hover. But showLabelByDefault() is true for any
non-healthy tone, so the label was rendered without horizontal padding
and spilled against the pill's right edge. Switch to py-1 px-2 to match
the warning and reconnecting branches, which already pad for an
always-visible label.
2026-05-19 09:31:38 +01:00
rcourtman 391cea1b13 Fix managed hot-dev LAN browser binding 2026-05-19 09:08:32 +01:00
rcourtman 0c1cdf7488 Add opt-in Proxmox LXC Docker inventory 2026-05-18 23:44:24 +01:00
rcourtman f0ad7f1284 Fix TrueNAS fractional uptime decoding
Refs #1469
2026-05-18 23:12:27 +01:00
rcourtman 91d7102a27 Make managed hot-dev verification deterministic 2026-05-18 17:29:50 +01:00
rcourtman 6feb85245f Stabilize first-session settings handoffs 2026-05-18 17:27:03 +01:00
rcourtman 84f627e22a Move Proxmox host drawers to the host table 2026-05-18 17:19:32 +01:00
rcourtman 5a405f7f07 Protect inside-guest Docker visibility boundaries 2026-05-18 14:16:15 +01:00
rcourtman dca06d6a55 Clarify Docker update check failures 2026-05-18 11:36:56 +01:00
rcourtman 8511016bd8 Share per-disk drawer card between Docker and node drawers
Extracts the per-disk Storage card introduced for the Docker host drawer
into a shared `DrawerDiskListCard` under `components/Workloads/` and
threads an optional `disks` prop through `NodeDrawer` →
`NodeDrawerOverview`. The Docker drawer now imports the shared component
instead of carrying its own copy, and `NodeDrawerOverview` renders the
shared card whenever the caller supplies two or more disks (falling back
to the existing aggregate Storage rows otherwise).

`ProxmoxNodesTable` is left untouched in this commit because the file is
currently dirty under another agent's lane; wiring the `disks` prop on
its `<NodeDrawer>` call site is a separate one-line follow-up once that
lane settles.
2026-05-18 11:30:54 +01:00
rcourtman 8302229a26 Refine workload sparkline I/O density 2026-05-18 11:29:15 +01:00
rcourtman 08e855483f Show Docker container update status in rows 2026-05-18 11:28:00 +01:00
rcourtman e3cce1efba Show per-disk breakdown in Docker host drawer Storage card
The previous Storage card surfaced only the aggregated host.disk values,
which on multi-drive hosts (Unraid in particular) is just the rootfs and
misses the data drives entirely. Drawer drill-down should be more
informative than the row's hover tooltip, not less.

When the host's agent reports multiple disks, the Storage card now spans
two columns and lists each disk with mountpoint, percent, used/total, and
a threshold-coloured bar that matches the StackedDiskBar tooltip palette.
Single-disk hosts keep the existing aggregate Usage/Total/Free rows.
2026-05-18 11:21:53 +01:00
rcourtman 1ebcf37525 Make Docker I/O columns telemetry-aware 2026-05-18 11:08:00 +01:00
rcourtman 0ebe2ab8c7 Tighten Docker host drawer overview field access
Replace inline `as { current?: number }` / `as { usage?: number }` casts on
the memory union with a `getNumericField` helper, and drop the stray
`agent()?.connectionState` lookup (that field lives on vSphere metadata,
not the agent telemetry record) so the Connection row reads the host
status directly.
2026-05-18 11:04:46 +01:00