Commit Graph

748 Commits

Author SHA1 Message Date
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 86ac84f68e Keep inline drawer expansion local 2026-05-19 19:36:22 +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 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 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 77cfd6aa4a Fix typecheck of identified-service test fixtures
Pre-push tsc rejected the discovery test fixtures because ResourceDiscovery
has more required fields than the minimum needed to exercise the new
identified-service helper. Widen the vi.fn return type so
mockResolvedValueOnce accepts partial-record fixtures, and cast the
fixtures through `unknown as ResourceDiscovery` (matching the existing
pattern used for the ports/facts subobjects in the same suites).
2026-05-17 23:24:53 +01:00
rcourtman fe7b618e8f Surface identified service in GuestDrawer overview
Discovery records carry rich identification (service name, category,
ports, cli access, paths) but until now they only rendered inside the
Discovery sub-tab. Operators opening a workload drawer to look at memory
or backups had no signal that Pulse already knows the LXC is "Homepage
Dashboard" or the Docker container is "Proxmox Backup Server" — they
had to switch tabs to see it.

Add an "Identified Service" card to GuestDrawerOverview that shows the
service name, category, port count, CLI access hint, and confidence
percent. The card mirrors the existing System/Guest Info/Backup card
shapes so it slots into the same flex grid.

useGuestDrawerState passively loads the discovery record via getDiscovery
(no scan triggered — manual scans, progress UI, and approval prompts
stay owned by DiscoveryTab and useDiscoveryTabState). A new presentation
helper, getDiscoveryIdentifiedSummary, packages the record into the
display shape and returns null when the record has no meaningful
identification — same gate as the Discovery tab's hasValidDiscovery, so
both surfaces render or hide together.

Contracts: amend frontend-primitives Completion Obligations to fence the
new identified-service reducer to discoveryPresentation.ts, and amend
performance-and-scalability Current State to require that out-of-tab
discovery surfacing stay passive (no scans). Verification via
discoveryPresentation.test.ts and GuestDrawer.test.tsx covers the
populated/empty/null branches and the drawer card render.

Workloads table row surfacing is the natural next step but blocked
behind the parallel agent's in-progress WorkloadPanel/WorkloadsSurface/
useWorkloadsState changes; revisit after those land.
2026-05-17 23:21:11 +01:00
rcourtman 7fac636bd5 Include containerId in Discovery resolver Pick type
Follow-up to the previous commit: resolveDiscoveryTargetForWorkload now
reads guest.containerId, so the Pick<WorkloadGuest, ...> input type must
include it or tsc rejects the property access.
2026-05-17 21:02:33 +01:00
rcourtman 25ae258bf4 Target Docker Discovery by container id, not workload hash
For app-container workloads, resolveDiscoveryTargetForWorkload was
populating the discovery resourceId with guest.id — the frontend's
synthetic workload hash (e.g. "app-container-ed79032e5852577f"). The
backend wraps that as docker exec <id> sh -c '...', the Docker daemon
has no container by that name, so every scan came back with empty
stdout and the AI returned "Unknown Infrastructure Resource" at
confidence 0. To the user the Discovery sub-tab looked empty after a
"successful" run.

guest.containerId is already populated from resource.docker.containerId
for Docker-managed app-containers (useWorkloads.ts:450). Use it as the
discovery resourceId so the agent can actually docker exec into the
right container. VM and system-container branches were already correct
because they use vmid for pct exec / qm guest exec.

Fix the existing workloadTopology test that locked in the broken
behavior, and add containerId to the workloads.test.ts fixture that
exercises the same resolver branch.
2026-05-17 20:59:05 +01:00
rcourtman d20671a736 Expose Discovery model override in AI settings
The backend has had DiscoveryModel + GetDiscoveryModel() with the
shared-default fallback alongside ChatModel and PatrolModel, but the
settings UI only surfaced Assistant and Patrol overrides. Discovery
silently used whatever the shared default was set to, with no way to
dial it independently — yet Discovery is the surface where a cheaper
model (e.g. Haiku) is most often the right tradeoff.

Thread discoveryModel through the form state, load, save payload, and
failure analyzer, and render a third picker in the Model Overrides
section mirroring the Assistant/Patrol pattern. Generalize the section
header and shared-default copy so they no longer enumerate surfaces.

Extend the frontend-primitives contract to recognize Discovery alongside
Assistant and Patrol as a per-surface override, and lock the picker
presence into settingsArchitecture.test.ts.
2026-05-17 20:39:46 +01:00
rcourtman b7ce2b6cc0 Map Discovery app-container to docker at API boundary
Frontend canonicalizes Docker containers as app-container, but the
Discovery API only accepts docker. Without translation, every Discovery
trigger for a Docker container 400'd at type validation before any agent
dispatch — surfaced to users as "unsupported resource type 'app-container'".

Mirror the existing pod -> k8s translation in toDiscoveryAPIResourceType.
The agent transport layer already handles ResourceTypeDocker via "docker
exec" on the connected agent host; only the URL-path resource type needed
the alias.
2026-05-17 20:35:06 +01:00
rcourtman 5e789f9a1d Align AI docs with model-owned reasoning 2026-05-17 17:59:22 +01:00
rcourtman dcd59bf11b Fix Proxmox node version display
Read canonical Proxmox pveVersion metadata for host rows and share compact PVE version formatting across the Proxmox page and node headers. Add contract and proof coverage for the new Proxmox metadata fields.
2026-05-17 13:59:37 +01:00
rcourtman edc8bd5df0 frontend(platform-first): drop alerts/findings/drawer cross-jump chips and dead resourceLinks helpers
Three surfaces still rendered "Open in Infrastructure / Workloads / Storage /
Recovery" chips that 404'd after the previous platform-first migration
commit; remove them and delete the now-orphaned resourceLinks helpers.

- AlertResourceIncidentsPanel no longer renders surface-link chips.
- FindingsPanel (Patrol) no longer renders surface-link chips; the
  useResources lookup is gone with them.
- resourceDetailDrawerOperationalModel.buildRelatedLinks now returns only
  service-detail links (PMG thresholds); cross-jump injection through
  buildResourceSurfaceLinksForResource was retired.
- resourceLinks.ts: delete buildInfrastructureResourceHref /
  buildInfrastructureResourceLink / buildInfrastructureHrefForWorkload /
  buildWorkloadsHrefForResource / buildStorageHrefForResource /
  buildRecoveryHrefForResource / buildResourceSurfaceLinksForResource /
  buildResolvedResourceSurfaceLinks, the dedupeResourceSurfaceLinks /
  resolveKubernetesContextForResource / resolveHostHintForResource /
  resolveDockerWorkloadsHintForResource / hasMergedSource /
  resolveStorageRouteSource helpers, and the ResourceSurfaceLink /
  ResolvedResourceSurfaceLinkOptions types.
- Tests: resourceLinks.test.ts, FindingsPanel.links.test.tsx,
  resourceDetailDrawerOperationalModel.test.ts, AlertResourceIncidentsPanel
  .test.tsx, FindingsPanel.test.ts, Alerts.helpers.test.ts,
  frontendResourceTypeBoundaries.test.ts updated to assert the removal
  contract instead of the old link-rendering contract.
- Subsystem contracts (alerts, patrol-intelligence, frontend-primitives,
  storage-recovery, unified-resources) gain Current-State notes
  documenting the chip-strip removal and the helper deletion so future
  work doesn't resurrect the legacy cross-jump surface.
2026-05-16 20:42:47 +01:00
rcourtman abb6f86aeb frontend(platform-first): retire legacy top-level routes and cross-link affordances
Primary nav moved to platform-first (Proxmox/Docker/K8s/TrueNAS/vSphere) and
the standalone Infrastructure/Workloads/Storage/Recovery/Ceph routes were no
longer reachable from the UI — they only persisted as deep-link fallbacks and
as cross-jump targets for command palette / shortcuts / drawer links.

Code:
- RuntimeHome and NotFound redirect to /proxmox/overview; kiosk-mode
  redirect follows the same canonical home.
- App.tsx, routing/navigation.ts, routing/routePreload.ts, AppLayout.tsx
  drop the legacy routes, preloaders, tab IDs, and tab-title entries.
- Old page wrappers (Infrastructure/Workloads/Storage/Recovery/Ceph) and
  the orphan CephServiceIcon and workloadsLink helper are deleted; the
  underlying feature surfaces (InfrastructurePageSurface, WorkloadsSurface,
  StorageSurface, RecoverySurface, ProxmoxCephTable) stay embedded inside
  platform pages.
- Command palette and keyboard shortcuts now expose the five platform
  pages (g p/d/k/n/v) plus Patrol/Alerts/Settings; legacy g i/w/s/b are
  retired.
- K8s Namespaces/Deployments drawer "view pods" buttons route to
  /kubernetes/pods (the legacy ?context= + ?namespace= filter doesn't
  carry forward yet — the new sub-tab doesn't consume those params).
- Workload-row node-name click, host-row workloads icon, resource
  correlation/change drilldowns, and the PBS "Open Recovery Events" link
  render as plain text or are dropped — platform pages already surface
  the related data in adjacent sub-tabs.

Governance:
- Subsystem contracts updated to match: storage-recovery and
  performance-and-scalability drop the retired page wrappers from their
  Canonical Files lists; ai-runtime, cloud-paid, frontend-primitives, and
  unified-resources gain Current-State notes describing the platform-first
  redirect, palette/shortcut re-anchoring, and cross-link affordance
  removal so future work doesn't resurrect the retired paths.
- status.json L8 (First-session & UX polish) evidence: the retired
  pages/Infrastructure.tsx wrapper is replaced by pages/Proxmox.tsx as
  the canonical post-redirect landing surface.
- registry.json: 18 retired-file entries removed from owned_files,
  path-policy match_files, and exact_files across the route-shell-and-
  operations, workload-presentation, storage-recovery, and unified-
  resources subsystems. Surviving entries continue to cover the proof
  set — the embedded feature surfaces remain canonical and the retired
  page wrappers were thin shells with no remaining proof obligation.
- canonical_completion_guard_test.py and subsystem_lookup_test.py
  retargeted: the recovery and storage product-surface fixtures now
  exercise the canonical components/* surfaces instead of the retired
  pages/* shells, matching the new registry shape.

Tests: typecheck clean. Vitest 5820 pass / 6 pre-existing fail
(PageControls.guardrails, useStoragePoolDetailModel, ApprovalSection x3,
frontendResourceTypeBoundaries:1018) — all failing on HEAD before this
change with clean porcelain on the offending source files. Python
canonical_completion_guard_test (141) and subsystem_lookup_test (183)
green.
2026-05-16 18:57:37 +01:00
rcourtman 7938f28de4 platforms: scale K8s clusters to 3 + fix VMware storage source matching
Two specific platform-page quality issues from the audit:

1. **/kubernetes/overview only had 1 cluster.** Bumping the K8s cluster
   count past 1 had been deferred because the prior
   monitor-broadcast equivalence test compared the raw snapshot count
   to the broadcast count exactly, and broadcast's
   `coalesceBroadcastResources` + second-pass coalesce inside
   `convertResourcesForBroadcast` legitimately drops merge candidates
   that the raw snapshot keeps. Switch the test to compare against
   the canonical snapshot count within a ±5% tolerance so future
   fixture bumps stay green without loosening any of the test's
   exact-name and exact-identity assertions. With that in place, bump
   `K8sClusterCount` 1 → 3 in `internal/mock/generator.go`,
   `scripts/toggle-mock.sh`, and the matching
   `scripts/tests/test-toggle-mock.sh` so the canonical mock estate
   ships with production + staging + edge clusters end-to-end.

   Live mock survey: k8s-cluster: 3, k8s-deployment: 42, pod: 120,
   plus 15 K8s nodes merged onto their agent hosts.

2. **/vmware/storage looked empty under platform-page chrome.**
   `resolveStorageSourceKey` was reading only `storage.type` (the
   on-disk technology like `vsan`, `vmfs`, `nfs41`, `zfs-pool`) and
   never consulted `storage.platform` (the canonical platform key
   like `vmware-vsphere` or `truenas`). Source filter chip options
   were therefore generated as `vsan`, `vmfs`, `nfs41`, etc., and
   `forcedSourceFilter='vmware-vsphere'` had nothing to match.
   Prefer the canonical `storage.platform` tag when set, so VMware
   datastores group under `vmware-vsphere`, TrueNAS pools group under
   `truenas`, PBS datastores under `proxmox-pbs`, etc., for both the
   chip options and the embedded platform-page filter.

Browser verification (Playwright, chromium, live mock-mode dev runtime):
- 9 tests pass.

Targeted vitest:
- `src/features/storageBackups` + `src/utils/__tests__/sourcePlatforms.test.ts` +
  `src/components/Storage/__tests__/storageSourceOptions.test.ts` (31
  files / 141 tests) green.

Go tests:
- `go test ./internal/mock/... ./internal/monitoring/... ./internal/vmware/...`
  all green.

Contracts updated:
- `monitoring.md` Shared Boundaries: new K8s multi-cluster default,
  ±5% tolerance for the broadcast equivalence assertion.
- `deployment-installability.md` Shared Boundaries: toggle-mock.sh /
  DefaultConfig parity updated for the K8sClusterCount=3 baseline.
2026-05-16 11:43:41 +01:00
rcourtman f81490ca4f Keep discovery manual refresh visible 2026-05-15 23:55:22 +01:00
rcourtman cfc3fd1606 frontend(platforms): resolve resource platform family via sources fallback
The unified resource projection returned by /api/resources leaves
`platformType` empty on several canonical resource types (storage,
agent, pbs, app-container, vm, k8s-deployment, pod, etc.) under the
mock fixture path and parts of the live backend. Platform-first pages
were filtering on `resource.platformType` directly, so Docker /
Kubernetes / TrueNAS / vSphere pages rendered empty under mock mode
even though the resources existed and were tagged with the right
`sources` array (['docker'], ['kubernetes'], ['truenas'], ['vmware']).

Introduce `resolveResourcePlatformType(resource)` in
`frontend-modern/src/utils/sourcePlatforms.ts` as the canonical reader
for "what platform family does this unified resource belong to". It
prefers `resource.platformType` when present and falls back to the
resource's `sources` array via the existing
`resolvePlatformTypeFromSources` normalization, so client-side family
grouping behaves identically against mock fixtures and live backends.

Each platform page model now buckets resources through that helper:
- dockerPageModel.ts
- kubernetesPageModel.ts
- truenasPageModel.ts
- vmwarePageModel.ts

Browser verification (Playwright, chromium, against live mock-mode
Pulse dev runtime):
- 4 no-data tests (stubbed empty /api/resources): all 4 pages render
  sub-tab chrome and surface empty state.
- 4 populated tests (live mock backend): each platform asserts at
  least one canonical row renders on its data-bearing sub-tabs:
  - docker: /docker/overview (Hosts), /docker/containers
  - kubernetes: /kubernetes/pods, /kubernetes/deployments
  - truenas: /truenas/storage
  - vmware: /vmware/vms, /vmware/storage
  All 8 tests pass.

Verification artifacts staged:
- sourcePlatforms.test.ts extended with resolveResourcePlatformType
  cases (19 tests pass).
- 68-platform-pages-shell.spec.ts extended with populated-state
  assertions per platform.

Contracts updated:
- unified-resources.md Shared Boundaries: resolveResourcePlatformType
  is the canonical reader for unified-resource platform family.
- frontend-primitives.md Extension Points: same.

Remaining mock fixture gaps (intentionally not asserted populated in
this commit; tracked for fixture extension):
- docker/services: default mock fixtures do not expose docker-service
  resources at the /api/resources boundary.
- kubernetes/overview, kubernetes/nodes, kubernetes/services:
  KubernetesClusters are generated but k8s-cluster, k8s-node, and
  k8s-service resource projections are not surfaced.
- truenas/overview, truenas/apps: no TrueNAS agent or TrueNAS-scoped
  app-container resources in default fixtures.
- vmware/overview: no VMware ESXi-host agent resources in default
  fixtures.
2026-05-15 23:38:40 +01:00
rcourtman 0b11a29727 Compact workload history charts 2026-05-15 18:49:42 +01:00
rcourtman d213ea3e29 Add Proxmox platform table metrics 2026-05-15 18:13:27 +01:00
rcourtman 14d3284233 Remove Assistant prompt routing heuristics 2026-05-15 16:10:58 +01:00
rcourtman 99fce8e5a3 Remove Assistant status routing residue 2026-05-15 15:19:26 +01:00
rcourtman eac7dfe9ef Make Patrol reasoning model-owned 2026-05-15 12:22:52 +01:00
rcourtman 376b2f80fc Simplify Patrol Assistant handoffs 2026-05-15 10:17:10 +01:00
rcourtman 7d6b447c59 Simplify recovery presentation and type contracts 2026-05-14 21:08:54 +01:00
rcourtman 3580a5ed6d Clarify storage topology and recovery guards 2026-05-14 20:51:32 +01:00
rcourtman a6c460daa0 Stabilize infrastructure realtime identity 2026-05-14 11:50:29 +01:00
rcourtman 321f563a52 Surface blocked workload inventory sources
Show workload-capable source failures on Workloads and keep matching Proxmox host agents attached to their API source when inventory collection is blocked.
2026-05-14 00:22:18 +01:00
rcourtman cbe595572b Improve action audit refusal presentation
Present stable refusal prefixes through shared frontend helpers and show verification outcomes in resource action history without exposing raw refusal tokens. Extend action audit tests, drawer coverage, and VMware history spec evidence.
2026-05-13 23:03:12 +01:00
rcourtman f024d3b560 Align action audit verification projection 2026-05-13 18:36:00 +01:00
rcourtman 53ebbf97f8 Harden recovery activity timeline ranges 2026-05-13 18:32:25 +01:00
rcourtman 3cbd49cb54 Align Patrol coverage caveats with full runs 2026-05-12 21:33:44 +01:00
rcourtman 5b18438528 Use checked wording for limited Patrol recency 2026-05-12 17:53:53 +01:00
rcourtman 80c061cd45 Separate Patrol runtime issues from findings copy 2026-05-12 17:43:28 +01:00
rcourtman 450e7516f0 Keep Patrol findings from showing all-clear copy 2026-05-12 17:34:30 +01:00
rcourtman edda19aadf Catch up frontend tests with collapsed Patrol details and refactored FindingsPanel 2026-05-11 23:37:21 +01:00
rcourtman 2725a325f2 Consolidate AgentIntegrationsPanel doc links into shipped AGENT_SUBSTRATE.md 2026-05-11 23:25:55 +01:00
rcourtman 68e2100955 Suppress empty metric values on state-alert Assistant handoffs
The Pulse Assistant briefing and prompt for a powered-off alert
rendered "Current value 0.0%; threshold 0.0%" because the backend
sends value=0 and threshold=0 for state alerts (which have no
metric semantics). That line is misleading to the operator and
gives the LLM no useful signal.

Adds isMetricAlertType / isStateAlertType helpers to
frontend-modern/src/utils/alerts.ts naming the state-alert set
(powered-off, unreachable, offline, host-offline, connectivity,
docker-container-state, docker-container-health,
docker-host-offline). State alerts represent binary or enumerated
conditions, not metric threshold crossings.

The alert handoff builder routes through that helper:
  - Briefing detailLines omit the value/threshold line when the
    alert is a state alert.
  - Prompt omits the **Current Value:** and **Threshold:** lines.
  - Prompt now includes **Message:** so the actual signal is
    surfaced (was previously dropped from the prompt).
  - Prompt step 2 swaps "Check related metrics" for "Check what
    changed recently for this resource (state events, recent
    commands, related alerts)" — the right question for a
    binary-state alert.

Two new tests cover the state-alert and metric-alert branches.
2026-05-10 23:01:21 +01:00
rcourtman 77479fb9ca Rename Patrol schedule "Disabled" preset to "Off"
The Configure Patrol drawer paired a master "Patrol Running" toggle
with a "Run every: Disabled" dropdown for the no-schedule case. Two
contradictory wordings on the same surface — easy to misread the
schedule label as "Patrol is disabled" when the master toggle is
actually on and triggered/manual runs are still working.

Rename the zero-interval preset to "Off" and rewrite the helper
text to spell out that manual runs and alert/anomaly triggers still
fire when the schedule is off. The change is naming only; the
underlying interval value (0) is unchanged so persisted settings
continue to round-trip.

Updates the canonical-presets test assertion to match.
2026-05-10 19:40:16 +01:00
rcourtman a17f879a19 Stop redacting resource names in remaining operator-local UI
Continues the policy-redaction scope fix from abdde303a. The same wrong
invariant -- "operator-local UI redacts when policy says redact" -- was
also encoded in:

- alertResourceTableModel.getAlertResourceLabel (the /alerts table that
  feeds AlertResourceTableMobile and AlertResourceTableRow)
- AlertResourceIncidentsPanel (the incident card shown after the operator
  clicks into a resource)
- infrastructureSelectors.matchesSearch (/infrastructure search filter,
  whose haystack already includes raw hostname/ips so redacting the
  displayName was internally inconsistent)
- useResources filter().search (the shared resource hook's search
  predicate, used by Settings panels, /storage, /alerts, FindingsPanel)
- recoveryRecordPresentation.getRecoveryLinkedResourceLabel (recovery
  rollup labels rendered in /recovery)
- Settings/ResourcePicker (the operator's selection UI for the Reporting
  Panel; the picker is local even when the report itself transmits)

All six surfaces are operator-local. Per docs/PRIVACY.md, resource-policy
redaction applies "Before non-local model requests leave the instance" --
it is a transmission-boundary policy, not a local-rendering policy. The
operator must see their own resource names to recognize, search, and
configure them.

Switch each callsite to getPreferredInfrastructureDisplayName. Invert the
five test fixtures (ResourceTable, ResourcePicker, infrastructureSelectors,
UnifiedResourceTable performance contract, WorkloadsSurface performance
contract) that locked in the old redaction-in-local-UI invariant.

Cloud-bound callers (AI Chat, organization sharing prepare-payload, the
resource-detail drawer's governance meta panel) still call
getPreferredResourceDisplayName and continue to redact -- their tests
are unchanged. The only call site of the governed helper that is left
not yet reclassified is problemResourcePresentation, which currently has
no production consumers; leaving it untouched.
2026-05-09 20:16:37 +01:00
rcourtman 3f783a7d98 Distinguish auto-suppressed dismissals from manual operator dismissals on the row
Both serialize as DismissedReason="expected_behavior" but tell
different stories — the auto-dismiss is Pulse staying quiet because
the operator scheduled maintenance or marked the resource
intentionally offline, while the manual dismiss is the operator
deciding the finding is expected. The lifecycle metadata
(operator_state_cause) already distinguishes them; this slice surfaces
the distinction on the FindingsPanel row.

Adds getOperatorStateDismissCause and formatOperatorStateDismissCauseLabel
helpers (the TS mirror of findOperatorStateDismissCause from slice 37).
Renders an "auto: maintenance" / "auto: intentionally offline" badge
next to the existing dismissed-reason badge when the lifecycle scan
finds operator_state_cause on the most-recent dismissed event.

The newest-first scan order is the same as the Go side: a manual
dismissal that supersedes an earlier auto-dismiss reports as manual,
so a stale auto-dismiss cause does not falsely badge the row after
an operator override.
2026-05-09 19:10:27 +01:00
rcourtman 3ddfd224aa Clarify the 24h alert overview stat label
The Acknowledged / Last 24 Hours / Workload Overrides stat row on
/alerts had an ambiguous middle card: "Last 24 Hours" with a bare
number doesn't say what is being counted. Rename the label to
"Triggered (24h)" so the metric (alerts that fired in the window)
is readable at a glance. The underlying total24h calculation is
unchanged.
2026-05-09 18:39:42 +01:00
rcourtman 6e0b951a27 Strip Proxmox pve-manager wrapper from version badges
Proxmox APIs return version strings in the form
'pve-manager/9.1.9/ee7bad0a3d1546c9' (package-name/version/git-hash).
Without stripping the wrapper, badges rendered as
'PVE pve-manager/9.1.9/ee7bad0a3d1546c9', which truncates to the
unreadable 'PVE pve-manage...' in the SYSTEM column on the
infrastructure dashboard.

Extend normalizeVersion to detect and unwrap the package/version/hash
shape (also covers 'proxmox-backup-server/4.0/...' and similar). Now
badges read 'PVE 9.1.9', 'PBS 4.0', etc. as expected.
2026-05-09 18:03:42 +01:00
rcourtman 8504d97c5b Humanize raw backend errors on the connections list
The Infrastructure connections table surfaced lastError.message verbatim
from the backend, which produced user-hostile rows like
'poll_nodes failed on delly: Get "https://192.168.0.134:8006/api2/json/nodes":
context deadline exceeded (Client.Timeout exceeded while awaiting headers)'.

Add a connectionErrorPresentation utility that maps the common Go error
chains we see in practice to a short headline plus a remediation hint:

- timeout / deadline exceeded -> 'Connection timed out'
- DNS lookup failures -> 'Host not found'
- connection refused, unreachable network, TLS issues, 4xx, 5xx etc.

Unknown errors fall back to the raw message stripped of the most common
implementation prefixes (the poll_<x> failed on <y>: + Get "URL": noise)
so they remain readable.

Wire the helper into useConnectionsLedger so every row goes through it;
the rest of the rendering chain stays unchanged.
2026-05-09 13:10:29 +01:00
rcourtman 06ebe55e13 Add Copy summary action to share findings as paste-ready Markdown
Operators frequently need to delegate a finding by pasting it into Slack,
email, or a ticket. Currently they have to manually copy each piece —
title, description, impact, recommendation — out of the expanded card.

Adds formatFindingForClipboard helper that produces Markdown mirroring the
seven-question schema's render order (severity + title + resource, then
description, impact, recommendation, plus trust signals). Wires a Copy
summary button between Explain and Discuss in the expanded card, routing
through the shared copyToClipboard helper with success/error notifications.

Investigation evidence and rollback plans are intentionally omitted —
those are conversation context for the Assistant flow, not "share this
finding" context for chat or tickets.
2026-05-09 13:00:04 +01:00
rcourtman 7593097e04 Surface verified-resource coverage on the Patrol recency line
Third wedge of the Patrol page IA reframe. The recency line "Last full
patrol: 3m ago" tells the operator when Pulse last ran but not what it
covered. Adds a coverage signal so the line reads "Last full patrol: 3m
ago — verified 47 resources" — operators see temporal AND coverage state
without scrolling.

Extends PatrolRecencyPresentation with an optional resourcesChecked
populated from PatrolRunRecord.resources_checked on the latest completed
run. Field is intentionally optional (omitted when zero) so a degenerate
zero-coverage run doesn't render "verified 0 resources" as an alarm.
2026-05-09 12:54:14 +01:00