Commit Graph

7552 Commits

Author SHA1 Message Date
rcourtman fd4cb9c8d5 feat: replace linkedResourceId text input with resource picker dropdown
The 'Link to resource' field in the availability target form was a
plain text input requiring the user to know and type a resource ID.
Replace it with a FormSelect dropdown that lists all known resources
grouped by platform (Agent, Docker, PVE, PBS, etc.), with resource
type labels in parentheses for context.

Default option remains 'Auto-detect by IP (recommended)'. A fallback
option appears for linked resources not currently in the discovery
list.

Also fixes a pre-existing platformType typo ('proxmox' → 'proxmox-pve')
in the availability probe presentation test.
2026-06-26 12:38:54 +01:00
rcourtman 62c2b765d0 feat: attach availability checks as facet on known resource row
Agentless availability checks (ICMP/TCP/HTTP) were always minting
standalone network-endpoint resources, leaving them disconnected from
the known Proxmox/Docker guest they actually monitor. This made
availability evidence invisible on the platform resource row where the
user expects it, per the performance-and-scalability bounded-row
contract.

Backend (unified-resources ingest):
- Add LinkedResourceID field to AvailabilityData and AvailabilityTarget
- resolveAvailabilityLink: explicit link first, then exact-IP unambiguous
  correlation; skip hostname-only (lossy); guard against overwriting a
  different target's facet
- Unlinked/unmatched probes still mint network-endpoint (fallback)

Frontend:
- Relax getAvailabilityProbePresentation for any resource with availability
- Add compact protocol badge to UnifiedResourceHostTableCard name cell
- Add optional 'Link to resource' field to availability target form
- Add linkedResourceId to frontend types

Contracts: api-contracts, unified-resources, monitoring,
performance-and-scalability, storage-recovery.

Governance: coverage_gap + candidate_lane in status.json.
2026-06-26 12:16:11 +01:00
rcourtman adf6c6f1e5 fix: treat absent onboot as no-autostart (Proxmox default) for alert suppression
parseProxmoxOnBoot returned nil when the onboot key was absent from a
valid config, but Proxmox's default is onboot=0 (do not start on boot).
This caused stopped VMs without an explicit onboot line (like
windows-runner and tails-anon) to still generate powered-off alerts
despite not being configured to autostart.

Now returns false for absent keys in non-empty configs. nil is reserved
for empty/failed config fetches and unrecognised values, preserving the
'unknown' fallback for genuine uncertainty.
2026-06-26 11:35:12 +01:00
rcourtman 47d6ad94db test: fix 4 pre-existing frontend test failures blocking clean suite
recoveryDatePresentation: locale-independent date assertion (en-GB
systems produce '9 Mar 2026' not 'Mar 9, 2026')
quickstartCopyContract: update 'tools' to 'capabilities' matching
docs/AI.md copy change
AIIntelligence: update stale empty-state text to full current copy
SharedPrimitives.guardrails: align guardrail + registry with
d79640c41 migration of ProxmoxBackupsTable from FilterButtonGroup to
FilterSegmentedControl

Full suite: 659 files, 6719 tests, 0 failures.
2026-06-26 11:26:16 +01:00
rcourtman 0f8e341bfb frontend: resolve bare-hostname management URLs to LAN IPs from resource identity
When a Proxmox node is configured with a bare hostname (e.g. "pi") in its
API endpoint URL, the "Open web interface" link produced an unresolvable
URL (https://pi:8006) because the user's browser cannot resolve local
hostnames via mDNS or /etc/hosts.

Add resolveGuestUrlWithIdentity() to substitute the resource's known LAN
IP (from identity.ips, preferring RFC 1918) when the URL hostname is a
bare label without dots. URLs that already use IPs or FQDNs are left
unchanged.

Verified: pi node link now resolves to https://192.168.0.2:8006 instead
of https://pi:8006. delly and minipc URLs unchanged.
2026-06-26 09:56:34 +01:00
rcourtman 0bb25722dd test: use relative date in alert lifecycle canonical test to avoid stale-date eviction
TestAlertLifecycleCanonicalChangesRemainWritable used a hardcoded date
(2026-03-20) that is now >90 days old, exceeding the incident store's
maxAge retention. trimLocked() evicted the incident shell immediately
after creation, causing GetTimelineByAlertIdentifier to return nil.

Switch to time.Now().UTC().Add(-1h) so the test never goes stale.
2026-06-26 09:37:51 +01:00
rcourtman 6bb147c1bb Suppress powered-off alerts for guests not configured to autostart
The alert engine fired powered-off alerts for every stopped VM and
container regardless of whether the guest was configured to autostart.
This meant intentionally-stopped guests (onboot=0) generated alarm
fatigue — in the live lab, 7 of 12 alerts were noise from stopped VMs
that are deliberately off.

Now the alert engine checks the Proxmox onboot setting before firing:
- onboot=true  → stopped is unexpected, alert (preserved behavior)
- onboot=false → stopped is expected, suppress
- onboot=nil   → unknown, alert (preserved behavior)

For containers, onboot is parsed from the already-fetched container
config in enrichContainerMetadata (no new API call). For VMs, the
config is fetched only for stopped VMs via GetVMConfig (one extra call
per stopped VM, running VMs are unaffected).

Models: add OnBoot *bool to VM and Container.
Alerts: add onboot-aware suppression branch in CheckGuest.
Monitoring: add parseProxmoxOnBoot helper and fetchVMOnBoot.
2026-06-26 00:22:59 +01:00
rcourtman 5456ca16a3 Remove redundant/noise elements from guest drawer overview
- Remove AI Context card: always duplicated the parent GuestDrawer's
  Discovery readiness banner (same prop, same data, always both rendered)
- Remove confidence percentage from Identified Service card: non-actionable
  forensic number that told users nothing useful
- Remove cliAccess text from Identified Service card: developer-facing
  jargon (pulse_control/target_host) shown truncated in tiny font;
  the Ask Assistant button already provides the action path
2026-06-25 23:48:54 +01:00
rcourtman 7d0b481e22 patrol: show actionable state badges on collapsed Patrol finding rows
Patrol findings hide raw investigation-status and investigation-outcome
badges per the patrol-intelligence contract, but the contract explicitly
allows plain-language actionable state labels (approval required,
investigating, verifying fix, fix failed). This adds a getPatrolFinding-
ActionableState helper and renders the resulting badge on the collapsed
Patrol row between the severity badge and the title, so the operator
sees each finding's state without expanding. The badge is absent when
no actionable state applies, leaving the default new-finding row clean.
2026-06-25 23:27:34 +01:00
rcourtman 370017b48d patrol: surface work-type composition in the Open work workspace description
Classify active Patrol findings by actionable work type (needs approval,
failed fix, in progress, recurring, new) and weave a compact composition
clause into the workspace description so the operator sees the nature of
their open work at a glance. The clause is empty when all findings are
plain new issues, so the single-finding experience is unchanged.

Advances the protection-posture-attention-queue strong-version checklist
item: Patrol groups operator work across findings, approvals, failed
checks, recurring issues, and unresolved incidents.
2026-06-25 23:19:06 +01:00
rcourtman 138088a073 patrol: render finding recommendation on the expanded finding card
The expanded Patrol/AI finding card rendered Description and (conditionally)
Impact, but never the backend-populated `recommendation` field — so the most
actionable content (concrete next steps, plain language) was invisible on the
card, even though it was included in the Copy-summary clipboard output. Render
it as a "Recommended next step:" line in the contract's Description -> Impact
-> Recommendation order.

Contract conformance with patrol-intelligence.md (FindingsPanel canonical
renderer; Impact sits "between Description and Recommendation") and the
2026-06-25 monitor-first strong-version checklist ("each item uses plain
language: ... recommended next step"). Shape fix only — surfaces existing
data, no new payload.

Governed path: FindingsPanel.tsx (patrol-intelligence subsystem, lane L6).
Work claimed against coverage gap protection-posture-attention-queue.

Refs #1234
2026-06-25 22:56:39 +01:00
rcourtman 83e8f53ac5 Recheck guest Docker inventory after checker setup
Force stale negative LXC Docker detections to re-run after the Docker checker is configured so explicit Proxmox guest Docker inventory repopulates after backend restarts.
2026-06-25 20:30:38 +01:00
rcourtman a4c5cfd26d Fix encoded resource type filters
Treat browser-encoded comma-separated resource type lists the same as literal comma filters so Docker and platform pages keep canonical inventory rows.
2026-06-25 20:20:35 +01:00
rcourtman be7156c462 ai: replace legacy segmentedButtonClass with canonical FilterSegmentedControl
Migrate the two consumers of the legacy segmentedButtonClass util onto the
canonical FilterSegmentedControl template so they match every other view
toggle in the app:
- FindingsPanel Active/All/Resolved (+Overdue) filter
- AICostDashboard 1d/7d/30d/90d/1y range toggle

Thread tone and disabled support through FilterSegmentedControl so the
canonical template is a true superset of the legacy util, then delete
segmentedButton.ts and its tests.
2026-06-25 19:15:33 +01:00
rcourtman d79640c415 proxmox: use canonical FilterSegmentedControl for backups view toggle
Replace the bespoke FilterButtonGroup variant="segmented" Date/Coverage
toggle with the canonical FilterSegmentedControl template used by the
other view toggles (GroupedTableModeSegmentedControl,
MetricDisplayModeSegmentedControl), so the surface matches the shared
compact segmented-control look instead of a one-off style.
2026-06-25 18:53:36 +01:00
rcourtman e573738977 Revert "proxmox: promote cross-source backup coverage onto the front-door overview"
This reverts commit 82b86dc698.
2026-06-25 18:25:50 +01:00
rcourtman 376b7b9b07 Fix local infrastructure proxy bypass
Bypass environment proxies for local/private infrastructure endpoints and surface blocked workload inventory sources on platform pages.
2026-06-25 18:17:12 +01:00
rcourtman 82b86dc698 proxmox: promote cross-source backup coverage onto the front-door overview
The Proxmox Overview led with node/workload telemetry that the Proxmox UI
already shows, while Pulse's unique cross-source value (which workloads are
protected across the estate, via Proxmox guests x PBS) was buried in the Backups
tab. Add a ProxmoxOverviewCoverageStrip at the top of the Overview that reuses
the existing recovery model + coverage strip from Backups, fetching the backup
payloads async (createResource, non-blocking) so the landing paint is unaffected
and the strip fills in once coverage resolves. Gated on guest inventory so it
renders only when there are workloads to cover.
2026-06-25 18:02:47 +01:00
rcourtman 092c9ee6c7 Fix Proxmox tag color style propagation 2026-06-25 17:14:00 +01:00
rcourtman e4c7926094 findings: add mode-gated Investigate action to the Patrol finding row
An un-investigated Patrol finding had no forward action on its row: the
Assistant primary action is intentionally suppressed on the Patrol surface
(issue #1078), so the primary area was empty and the row dead-ended at triage.
Add an Investigate primary action that starts the detect -> investigate ->
propose -> approve -> verify loop via the existing reinvestigate endpoint,
surfacing the already-wired InvestigationSection/ApprovalSection once a run
exists.

Investigation is disabled in Watch-only (monitor) mode by policy, so the action
is gated to autonomy modes that permit it (approval/assisted/full) and stays
hidden otherwise rather than erroring in the default mode. Watch-only findings
remain triage-only by design; the workbench value lands once an action mode is
enabled.
2026-06-25 17:00:07 +01:00
rcourtman 6d9fe4e17c Make alert investigation route to Patrol 2026-06-25 16:00:06 +01:00
rcourtman 7449d14ef7 Show Patrol open work in navigation 2026-06-25 15:22:51 +01:00
rcourtman 2f69202056 Clarify Patrol open work guidance 2026-06-25 15:02:38 +01:00
rcourtman 4b0e60a448 Contextualize Assistant launcher 2026-06-25 14:54:57 +01:00
rcourtman 6dce36fbab Clarify Patrol current-work copy 2026-06-25 14:37:04 +01:00
rcourtman 056cd59405 Restore Patrol as operator surface 2026-06-25 14:23:50 +01:00
rcourtman ad01d77ffa Promote Needs Attention as work surface 2026-06-25 13:38:54 +01:00
rcourtman 166384b8a9 Capture operator workbench product direction 2026-06-25 13:18:16 +01:00
rcourtman 6f97f220de test(patrol): restore client suite and add triggerPatrolRun scope cases
The previous commit accidentally overwrote the existing patrol client test
suite (it predated this change). Restore those tests and add the
triggerPatrolRun scope-body cases alongside them instead of replacing the file.
2026-06-25 08:53:50 +01:00
rcourtman dd2f4c07fe alerts: offer manual targeted Patrol check from the alert AI button
The alert AI button now offers a manual targeted Patrol check alongside the
existing context-only Pulse Assistant handoff, so the take-the-lead intent routes
to Patrol (the investigator) instead of overloading Assistant (the explainer).

POST /api/ai/patrol/run (HandleForcePatrol) accepts an optional scope body and
routes through the same TriggerScopedPatrol engine and scoped run record as
automatic alert-triggered work, not a new trigger route; empty body keeps the
legacy fleet-wide run. Scoped runs honour Patrol readiness but bypass the
full-run cadence gate, consistent with the existing scoped-cadence rule, and
carry resource identity only (no command or remediation payload).

Frontend: triggerPatrolRun(scope?) and a split AI button (patrolOption) on the
alert overview card whose menu runs the scoped check; icon/history rows stay
single-purpose. Contract updates land in the touched/dependent subsystem docs
(ai-runtime, api-contracts, agent-lifecycle, storage-recovery, frontend-primitives,
security-privacy, patrol-intelligence, alerts); subsystem_lookup_test line
expectation refreshed for the api-contracts shift.
2026-06-25 08:49:29 +01:00
rcourtman 1e9b785f41 alerts: stop promising investigation in Pulse Assistant handoff copy
The alert-to-Assistant handoff is governed context-only (alerts.md: it must
open the drawer with context rather than seeding a prompt or choosing a
diagnostic route), but the user-visible copy said "investigate", overselling
what the surface actually does. Align button.full, unlockedTitle, and locked
copy (EN/ES/DE) so the promise matches the behaviour. The visible alert-row
label was already honest; briefing internals kept as attached-context labels.
2026-06-24 23:53:34 +01:00
rcourtman 33fd7df64e frontend: isolate app scroll shell so page z-indexes stay under the assistant overlay
Pages like Patrol use high z-indexes (z-[200]) which leaked into the
root stacking context and painted above the Pulse Assistant overlay
(z-50 panel, z-40 backdrop) when it switched to overlay mode below
1200px, so page text rendered on top of the sidebar. Adding isolate
to app-scroll-shell makes it a stacking-context boundary, trapping all
page content below the overlay regardless of the z-index a page uses.
2026-06-24 23:15:03 +01:00
rcourtman ce7831e28c patrol settings: drop misleading 'here' from plan-locked Watch-only copy 2026-06-24 21:32:52 +01:00
rcourtman 68ede71fd8 patrol settings: describe Watch only for plan-locked installs
The Settings > Patrol "Patrol mode" section told every install to choose a mode
on the Patrol page, but plan-locked (free) installs have no mode choice there —
Watch only is their only mode and the paid modes stay off the free surface. The
copy was tier-blind and sent free users on a fool's errand.

Make the section tier-aware via the canonical patrol autonomy availability
resolver: plan-locked installs now read "This install runs Watch only. Patrol
monitors your infrastructure and reports issues here." while Pro/runtime installs
keep the choose-on-the-Patrol-page redirect. The Open Patrol button stays.
2026-06-24 21:26:37 +01:00
rcourtman 6b610ebed9 patrol: add finding-level at-need Pulse Pro handoff for plan-locked installs
The free Patrol working surface stays clean of ambient paid surfacing, but a
plan-locked operator who expands an active critical or warning finding now gets
one honest, non-salesy capability line in the empty primary-action slot: "Pulse
Pro can investigate and fix issues like this." A "Learn about Pulse Pro" action
appears there only when the upgrade-prompt policy allows it.

This is the single allowed contextual at-need prompt — gated to plan-locked
installs, active critical/warning findings, and the upgrade-prompt policy — so
discovery happens at the moment of genuine need without re-tainting the daily-use
surface. Adds getPatrolProInvestigationHandoff in patrolControlPresentation,
threads the handoff through PatrolIntelligenceWorkspace into FindingsPanel,
codifies the carve-out across the owner and dependent contracts, and registers
the patrolControlPresentation test as patrol-page-and-state proof.
2026-06-24 21:03:20 +01:00
rcourtman 0155bc45c0 frontend: reconcile cost trend average, cap model table, pair budget controls
Trend cards now average over the selected range (effective_days) so
avg/day x days matches the headline total, instead of averaging only
active days and overstating the run-rate.

Cap the provider/model table to the top 10 by spend with a show-all
toggle, keeping the default view on the cost drivers.

Fold 30-day Budget and Request Timeout into a single two-column row
and drop the negative-margin helper hack.
2026-06-24 18:46:53 +01:00
rcourtman 7e54d2e95c patrol: keep free working surface clean of paid-feature surfacing
Reverts the interim opt-in Patrol mode disclosure added in 8fab6e6e0. The
free Patrol surface stays a complete, clean monitoring tool: plan-locked
installs show only the positive Watch-only control with no paid modes,
disabled buttons, Pro badges, or disclosure of any kind in the daily-use
surface. Pro discovery belongs in Settings, the website/docs, and
contextual at-need prompts instead.

Firms up the patrol-intelligence contract (owner plus api-contracts and
frontend-primitives dependents) to drop the permissive carve-out entirely,
and updates the recorded decision to match. An opt-in list of unclickable
modes did neither job well: it was not clean enough to keep the free product
proud, and too passive to drive conversion.
2026-06-24 16:58:33 +01:00
rcourtman 8fab6e6e02 patrol: surface plan-locked modes via opt-in disclosure
Plan-locked (free) installs showed only a flat "Watch only" with no way to
discover that Patrol has other modes. Add an explicit "See what Patrol can do"
affordance beside the Watch-only control that reveals the four modes (paid
ones disabled and Pro-badged) on demand, so operators can discover the
capability tiers without turning the default header into a Pro-absence matrix.

Resolves a patrol-intelligence subsystem contract conflict (a permissive line
allowing disabled paid buttons vs a prohibition on rendering them) by
codifying opt-in disclosure as the rule across the owner and dependent
contracts: the default (closed) view stays positive Watch-only, and the paid
modes appear only behind the explicit affordance. Records the product decision
in status.json resolved_decisions.
2026-06-24 16:30:16 +01:00
rcourtman c47560ad69 Fix websocket state broadcast retention
Refs #1442
2026-06-24 11:16:27 +01:00
rcourtman 9619d39747 frontend: expose accordion expand state to screen readers
Provider and Service Context accordion toggles carried no aria-expanded,
so the rotated chevron was the only expand/collapse signal. Add
aria-expanded and aria-controls (linked to the panel id) on both
accordion families so assistive tech announces state.
2026-06-24 09:23:48 +01:00
rcourtman fd2b3ebc2c frontend: declutter Provider & Models cost dashboard and provider form
Cost dashboard: cut vanity cards (total tokens, provider/model pairs),
remove the broken duplicate-row "Usage by task" table, label trend
sparklines with the daily average instead of a misleading last-point
value, sort the provider/model table by spend so the cost driver
surfaces first, and collapse the dense unpriced-model list into a
concise note with a hover tooltip.

Provider form: stop auto-expanding every configured provider into a
wall of API-key inputs. Configured providers now start collapsed (the
per-provider badges and health callout still convey status); first-time
setup still expands the default provider, and saves preserve the
operator's own expand/collapse choices.
2026-06-24 09:17:02 +01:00
rcourtman 1f6fe16e49 Make Z.ai provider base URL user-overridable
Z.ai keys on the coding subscription get 429 'Insufficient balance' on the standard /api/paas/v4 endpoint. Add a per-provider BaseURLField to the Z.ai registry entry, a ZaiBaseURL override on AIConfig (returned by GetBaseURLForProvider when set, else the standard default), the handler request/response/apply plumbing, and a 'Custom Base URL' field on the Z.ai provider card so operators can point at /api/coding/paas/v4. Mirrors the existing OpenAI custom-base-URL override; the standard endpoint remains the default for pay-as-you-go users. Adds config/handler/frontend proofs and updates the ai-runtime, api-contracts, frontend-primitives, agent-lifecycle, and storage-recovery contracts.
2026-06-23 22:49:55 +01:00
rcourtman 9346d2f99d Fix type-to-search crash on undefined event.key
handleTypeToSearchKeyDown accessed event.key (toLowerCase, length) without a guard, so keydown events with no key (IME composition, unidentified keys) threw a TypeError and broke type-to-search on host surfaces such as the Provider & Models default-model picker. Early-return when event.key is absent; modifier-only keys stay truthy and are unaffected.
2026-06-23 21:47:34 +01:00
rcourtman 09fc029bd6 Canonicalize assistant provider registry
Add a registry-backed provider model for runtime metadata and chat-compatible transports.

Support Z.ai, Groq, Mistral, Cerebras, Together, and Fireworks through the shared chat-compatible client path while keeping native providers on their existing adapters. Expose provider metadata through settings and render the expanded provider set in the frontend.
2026-06-23 19:38:22 +01:00
rcourtman da0693b3b1 monitoring: resolve configured-host IPs off the monitor lock
getConfiguredHostIPs() and the new discoveryConfigSnapshot() now deep-copy config under a brief RLock and release it before resolving Proxmox/PBS/PMG hostnames via the injectable lookupConfiguredHostIP seam, so slow or blocked DNS can no longer stall monitor writers. The discovery IPBlocklist is the deduplicated merge of the operator-configured blocklist and the resolved configured-host IPs. Adds lock-freedom and merge coverage tests; updates the monitoring contract obligation.
2026-06-23 17:31:37 +01:00
rcourtman ee8a24e14a backend and governance: MCP contract, agent capabilities, API, and release-control
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
2026-06-23 17:26:15 +01:00
rcourtman 27ba85ddc6 frontend: Patrol intelligence surface, shared primitives, and platform parity
Patrol operator UI (header, workspace, banners, findings, autonomy controls); convergence of shared primitives (buttons, spinners, notices, badges); settings information architecture (Pulse Intelligence, plans and billing, agent integrations); platform parity restoration across Proxmox, Kubernetes, Docker, storage, memory, and alerts; i18n foundation.
2026-06-23 17:15:58 +01:00
rcourtman 550dff89e2 Preserve paid runtime README guardrail phrase 2026-06-17 09:34:25 +01:00
rcourtman d06c0a24b7 Govern verified support case handling 2026-06-16 16:46:46 +01:00
rcourtman 84f6302450 Fix Proxmox cluster snapshot polling read state
Refs #1437
2026-06-16 14:43:18 +01:00