Summary cards now show degraded/alerting/failing counts rather than raw
online/offline splits, giving operators an at-a-glance health posture without
drilling into the full resource list.
- InfrastructureSummary/infrastructureSummaryModel: adds degraded and alerting
counts derived from resource statuses and active alerts
- StorageSummary/StoragePageSummary/useStoragePageSummary: adds poolsDegraded
and disksFailing indicators, shows "all healthy" when zero degradation
- WorkloadsSummary/useDashboardWorkloadDerivedState: adds alerting guest count
from activeAlerts accessor, included in summary header counts
- RecoverySummary: adds aggregate health-state summary row
- useDashboardState: threads alertsEnabled through workload derived state
- Fix monitored-system cap test helper to use TierEnterprise so limits are
honored (self-hosted tiers are now uncapped per the v6 product model)
- Update registry and governance test snapshots to include
useStoragePageSummary.test.ts in the storage-product-surface proof set
The purchase-start handoff (/auth/license-purchase-start) requires a configured
PublicURL and fails silently on local self-hosted instances without one. Routes
ai_alerts, ai_autofix, relay, rbac, audit_logging, advanced_sso,
agent_profiles, long_term_metrics, and trial_expired to the self-hosted billing
plan page so upgrade CTAs remain functional on local instances.
These props were passed through the entire alerts component tree but are no
longer needed — AI alert gating is handled by the shared license/entitlements
layer without each surface needing its own capabilities fetch. Removes the
onMount capabilities load, the paywall-viewed tracking effect, and the
dependent prop chain through OverviewTab, HistoryTab, and all alert card
surfaces.
Updates all test files broken by the collapse of infrastructure sub-routes
(/install, /platforms, /connections) into one /settings/infrastructure entry.
Key contract deltas:
- settingsNavCatalog/Model: infrastructure-connections and infrastructure-install
tabs removed; infrastructure-systems is the single sidebar entry
- infrastructureWorkspaceModel: getInfrastructureWorkspaceViewFromPath renamed
to deriveAddStepFromLegacyPath; buildInfrastructureWorkspacePath now always
returns /settings/infrastructure regardless of argument
- settingsNavigationModel: deriveAgentFromPath and isProxmoxSettingsPath use
normalised (not canonical) path so Proxmox deep links still resolve to the
correct agent before the canonical-path redirect fires
- useSettingsNavigation: Proxmox agent sync moved before the redirect early-
return so deep links seed the correct selectedAgent
- InfrastructureInstallerSection: "Open Platform connections" button replaced
with informational text; navigate target updated to flat workspace path
- useInfrastructureDiscoveryRuntimeState: currentTab parameter removed (tab
collapse means discovery is always active when the workspace is mounted)
- SetupCompletionPanel: unified INFRASTRUCTURE_PATH constant replaces the two
distinct INFRASTRUCTURE_INSTALL_PATH / INFRASTRUCTURE_PLATFORMS_PATH
- All navigation call sites (Dashboard, DashboardStateCards, Infrastructure
empty-state) updated to /settings/infrastructure
- Contract updates: agent-lifecycle, api-contracts, frontend-primitives
Two test regressions introduced when agent-report tokens were allowed as
fallback auth for /api/auto-register:
1. Org mismatch was not checked: a token belonging to org-a could authenticate
a request whose context carried org-b. Add an explicit org consistency check
before setting authenticated=true in the fallback path.
2. The security regression test assumed only setup tokens could authenticate
auto-register. That contract has intentionally changed: agent-report tokens
can now authenticate but are restricted to updating existing nodes (403 for
new-node attempts). Update the test to assert the actual security boundary.
The isKnownDisconnected helper was building the key as
instanceType+"-"+instanceName ("pve-delly"), but the PVE
PollProvider's connectionKey function returns the bare instance
name ("delly"). PBS uses "pbs-"+name. The mismatch meant the
disconnected-node check always missed, rendering the server-side
stale-token detection inert.
Fix: use type-specific key construction matching the PollProvider
connectionKey implementations.
The cold-startup race: if Pulse and the agent restart together, the
monitor has no connection-health data when the agent calls
checkRegistrationWithPulse at startup. The server defaults to
registered=true (no known-disconnected entry), so the agent skips
re-registration even though the token is stale. The node stays broken
until the next manual agent restart.
Fix: after the initial runProxmoxSetup call, start a background goroutine
that waits 2 minutes (giving the monitor time to poll PVE and record
failure state), then rechecks every 5 minutes via RunHealthCheck.
RunHealthCheck only acts on types that have a local registration marker.
Types without a marker are skipped to prevent uncontrolled token rotation
when Pulse is temporarily unreachable — those need a full startup setup
cycle via RunAll.
Together with the two earlier commits this closes all three stale-token
scenarios: install-time 401, long-running stale state, and cold-startup
race.
Two gaps in the existing flow allowed a disconnected PVE node to stay
broken indefinitely even after the agent restarted:
1. Server-side: autoRegisteredNodeExists checked only that a PVE/PBS
instance existed in the config, not whether its connection was
healthy. A node with a stale token would return registered=true on
every check, causing the agent to skip re-registration forever.
Fixed: also consult GetConnectionStatuses(); return registered=false
when the monitor has a definitive disconnected entry so the agent can
rotate and re-register.
2. Agent-side: the type-specific registration marker was cleared only on
success. If rotation succeeded but the Pulse update failed (e.g.
transient network error), the old marker from a previous successful
registration persisted, leaving next-startup to skip setup again.
Fixed: clear the marker before entering the token setup/rotation
phase so any failure leaves the system in a retriable state.
Together these two fixes make the stale-token scenario self-healing:
the monitor detects the broken connection, the next agent startup sees
registered=false, clears its marker, rotates the token, and updates
Pulse — without manual intervention.
When the agent is reinstalled on a Proxmox host, it rotates the PVE API
token in Proxmox but the Pulse server's /api/setup-script-url endpoint
requires settings:write scope — agent tokens only have agent:report — so
the 401 aborted the update, leaving Pulse with a stale token and a
disconnected PVE node.
Three-part fix:
- server: accept agent API tokens on /api/auto-register for updating
existing nodes (new nodes still require setup-token auth)
- agent: fall through instead of aborting when setup token fetch returns
4xx; send X-API-Token header so the server can authenticate via the
agent token instead
- update: allow HTTP auto-update URLs for RFC 1918 private network
addresses (LAN installs without HTTPS no longer block auto-update)
The /api/agent/version endpoint was returning the git-describe version
(e.g. 6.0.0-rc.2+git.58.g53a9339.dirty) for dev builds, which is always
semantically newer than the binary's embedded version (v6.0.0-rc.1).
This caused agents to loop: check version → see "newer" → self-update →
restart → still same binary version → loop again.
The agent's guard ("skip if server reports 'dev'") only fires for the
literal string "dev". Fix: return "dev" whenever IsDevelopment is true,
which covers all git-built/dirty dev instances.
Picking PVE, PBS, or PMG from the Add System picker navigated to the
bare /settings/infrastructure/platforms/proxmox URL and used an
onSelectAgent state callback to choose the sub-tab. The URL was the same
for all three kinds, so the Proxmox sub-tab chrome (driven by the URL)
stayed on Virtual Environment regardless of the user's choice, making
PVE/PMG landings pixel-identical and PBS show mismatched header/data.
Route directly to the canonical deep link
/settings/infrastructure/platforms/proxmox/{pve|pbs|pmg} so the URL,
sub-tab selection, and data panel always agree.
The supervisor's backend health monitor in scripts/hot-dev.sh only
checked whether a `./pulse` process existed via pgrep. If the Go binary
hung, panicked into recovery without exiting, or failed to bind :7655
while the process stayed alive, the monitor saw a healthy process count
and never restarted, leaving the dev frontend talking to a dead listener.
Hit that twice in one session.
Add an HTTP probe of /api/health on the dev port. After two consecutive
5s polls where the process exists but /api/health is unreachable, kill
the process and restart the same way the dead/duplicate branches do.
Cut the poll interval from 10s to 5s so the worst-case unresponsive
window is ~10s instead of ~30s. Bring deployment-installability into
line by naming alive-but-unresponsive recovery as part of the dev-runtime
orchestration boundary, and cover the new branch with static-source
assertions in scripts/tests/test-hot-dev-bg.sh.
The connections-table flip in 00c6dc2dd removed the workspace's top-of-
page subtab strip but left the install panel and platforms workspace
rendering bare with no path back to the inventory table. Operators who
clicked Add a system, picked a platform, and landed on
/settings/infrastructure/install or /settings/infrastructure/platforms/*
had to leave Settings entirely and return to the tab to get back to the
table.
Render a persistent breadcrumb header above every non-inventory view
inside InfrastructureWorkspace: a "← Connections and Inventory" button
that navigates back to the bare /settings/infrastructure route, plus a
heading naming the current subview. Inventory itself is unchanged.
Bring agent-lifecycle rule 7 into line by requiring this back-to-
inventory affordance on every detail route under /settings/infrastructure
so deeper surfaces are never reachable without a visible return path.
Cover the new header on install and platforms subviews — and its absence
on the inventory landing — in InfrastructureWorkspace.test.tsx.
settingsTabPath('infrastructure-operations') and the initial-tab resolver
still returned /settings/infrastructure/install from the pre-flip
workspace, so clicking the Settings nav dropped users on the install
detail page instead of the unified Connections table shipped in
00c6dc2dd. Point the tab path and canonical resolver at the bare
/settings/infrastructure, extend deriveTabFromPath to recognise that
prefix as the infrastructure-operations tab, and bring
frontend-primitives rule 13 into line with the already-flipped
agent-lifecycle rule 7 so both contracts name the bare
/settings/infrastructure as the canonical landing. Update the three
guardrail suites (settingsRouting, settingsNavigation.integration,
settingsArchitecture) that encode the tab->path map so they reflect the
new landing.
Replace the three-subtab install/connect/inventory narrative with a
single unified connections view at /settings/infrastructure. The base
route now renders one alpha-sorted table whose rows cover every
monitored system — Proxmox VE, PBS, PMG, TrueNAS, VMware, and agent
hosts — under the same name/kind/method/status/last-reported shape.
Adding a new system goes through a single "Add a system" picker whose
tiles route operators straight into the right flow:
/settings/infrastructure/install for the agent choice and
/settings/infrastructure/platforms/<kind> for Proxmox, TrueNAS, and
VMware. The install, platforms, and operations routes remain reachable
as detail surfaces, and read-only sessions continue to redirect the
install view back to the inventory base and suppress the add-system
entry point.
Update the agent-lifecycle subsystem contract to reflect that the
workspace shell no longer mandates a "bare routes default to install"
first-host narrative or a three-subtab layout. Refresh the guardrail
and integration tests that pinned the old tab shape and the legacy
operations landing route.
The picker presents the six target kinds Pulse knows how to connect
(Proxmox VE, PBS, PMG, TrueNAS, VMware, agent host) as a single list
with a short description and a method badge, so operators choose what
they are connecting rather than which Pulse workflow to enter. The
component stays method-agnostic: the caller decides what to do with
the selection in a follow-up commit that wires it into the workspace
shell.
Introduce a single row shape that spans PVE, PBS, PMG, TrueNAS, VMware,
and agent-host systems so the infrastructure workspace can surface every
monitored system as one alpha-sorted table instead of splitting systems
across method-specific subtabs. The model maps per-provider status
semantics into a shared reporting/pending/offline/error/unknown vocabulary
and carries a canonical last-reported timestamp for consistent relative
rendering. The component is not wired into any route yet; it lands as a
library piece so the workspace shell can consume it in a follow-up commit.
Rename the third subtab to "Inventory" and hide the first-system
orientation card once any platform connection or agent resource exists.
In the installer, drop the two cross-pointer callouts, replace the
locked-state placeholder with a disabled command preview, collapse the
uninstall block into a details disclosure, and keep the Platform
connections CTA as an inline link next to the target-profile note.
Remove the dead CalloutCard branch from ProxmoxSettingsPanel and update
the architecture guardrails accordingly. Pin the "Inventory" label and
the orientation-card gating into the agent-lifecycle contract so the
workspace-shell rule stays aligned with the surfaced narrative.
Add a compact Docker/Podman/TrueNAS runtime chip beside the image in the
Workloads image column. The Type badge stays a kind taxonomy (Container);
runtime is a separate axis and now glance-readable without hovering the
badge tooltip.
OCI is a flavor of system-container, not a distinct workload kind, but
WorkloadTypePresentationKey had 'oci-container' with its own purple
badge — creating two presentation paths for the same thing. Group
headers in workloadSelectors had inline label strings
('App Containers', 'Container', 'Pods', 'VM') that drifted from the
row badge's PRESENTATION_MAP whenever either side changed.
Drop 'oci-container' from the workload-kind union and presentation
map; normalize 'oci-container' input to 'system-container'. Add
pluralLabel to WorkloadTypePresentation and route getWorkloadGroupLabel
through it so the row badge and group header share one source. Collapse
the matching branch in recoveryItemTypePresentation. Align the relevant
assertion tests with the canonical VM/LXC/Container/Pod labels.
useWorkloads.mapResourceToWorkload was hardcoding isOci:false and
osTemplate:undefined for every resource, so the OCI-specific branch
in useGuestRowState.typeInfo was unreachable. Backend already sends
isOci and osTemplate on resource.proxmox; the local APIResource shape
just didn't declare them.
Add isOci/osTemplate to ResourceProxmoxMeta and the local APIResource
proxmox shape, then forward the real values for system-container rows.
Simplify typeInfo to a single getWorkloadTypeBadge call with the OCI
tooltip carried via the title override, and update the GuestRow OCI
test to exercise the new flow end-to-end.
The title-only header still cost vertical space without adding signal.
These surfaces already identify themselves via the left nav tab state,
so the in-page heading was pure chrome. Remove the PageHeader import
and element on each of the four pages that still carried it.
Patrol retains its header because it hosts active controls; Alerts
retains SectionHeader because it orients between sibling tabs.
Follow-up to fc019780c. Ceph and the Patrol intelligence header carried
the same "let me narrate what this page does" blurbs under their titles;
Alerts routed five tab-specific descriptions through
getAlertsPageHeaderMeta().
Removed the Ceph and Patrol description props, dropped the description
field from each entry in getAlertsPageHeaderMeta() (and the six unused
ALERTS_PAGE_*_DESCRIPTION constants), and pared the Alerts SectionHeader
usage to title-only. Test updated to match.