Commit Graph

2970 Commits

Author SHA1 Message Date
rcourtman 87604edb21 Add hosted MSP signup front door to the cloud control plane
Adds public self-serve signup for the hosted MSP offering alongside the
existing individual Cloud signup page. New handlers serve /cloud/msp/signup,
/cloud/msp/signup/complete, and /api/public/msp/signup, gated behind the same
PublicCloudSignupEnabled flag. Per-tier Stripe prices
(CP_MSP_STARTER_PRICE_ID, CP_MSP_GROWTH_PRICE_ID, CP_MSP_SCALE_PRICE_ID) are
validated against the canonical msp_starter/growth/scale plan versions; the
page renders an explicit "not open yet" notice when no MSP price is
configured. Checkout sessions carry account_kind=msp metadata so the
provisioner seeds an isolated operator workspace. The cloud and MSP JSON
signup endpoints now share one checkout skeleton parameterized per path.
2026-05-28 22:38:15 +01:00
rcourtman 05abf07219 Add Kubernetes, TrueNAS, and vSphere alert targets 2026-05-28 18:23:14 +01:00
rcourtman 75d1a0477b Enforce upgrade readiness before v6 updates
Refs #1476
2026-05-28 17:22:01 +01:00
rcourtman e1d1ec1d7c Add update readiness checks for v6 upgrades
Surface server upgrade readiness on update plans so v5 agent continuity and agent token scope issues are checked before install.
2026-05-28 16:47:49 +01:00
rcourtman aa4a5fa631 Add agent provisioning capabilities 2026-05-28 16:31:19 +01:00
rcourtman f7a40f3a22 Add Proxmox setup doctor repair flow 2026-05-28 15:51:31 +01:00
rcourtman 79a501e092 Lighten AI intelligence handler test setup 2026-05-28 15:10:39 +01:00
rcourtman 7297aae4af Fix late issue triage regressions
Refs #1341

Refs #1429

Refs #1469

Refs #1472

Refs #1476

Refs #1481
2026-05-28 14:49:20 +01:00
rcourtman 47e9eb9aea Reconcile Ceph pool alert identity
Refs #1341
2026-05-28 14:42:43 +01:00
rcourtman bbf047f7c4 Reduce API chart contract seed volume 2026-05-28 14:32:19 +01:00
rcourtman 88c3e7b3b2 Run ceph pool alerts for agent-sourced clusters
Fixes #1341

Forward-port of the release/5.1 fix (ce1607694).

When a Pulse host-agent reports Ceph data, ApplyHostReport upserts the
cluster into state but only the Proxmox-API polling path ran
checkCephPoolStorage. Users with agent-reported Ceph (instance prefix
"agent:hostname") saved per-pool overrides under agent-prefixed IDs
that the alert manager never evaluated. The threshold appeared to
save (and showed Custom in the UI), but the polling cycle was
checking a different storage ID, so the alert silently stayed
dormant. Run checkCephPoolStorage right after the agent upsert so
the override key actually drives evaluation.
2026-05-28 14:16:14 +01:00
rcourtman 5a22b04c4c Harden audit log store failures
Refs #1464
2026-05-28 13:44:20 +01:00
rcourtman 80aab47da6 Stop swallowing alert config persistence errors
Forward-port of the release/5.1 fix (2c46c6c2d).

UpdateAlertConfig used to log SaveAlertConfig failures and still tell
the client "saved successfully", leaving the in-memory state with the
new override but the on-disk file untouched. On the next config reload
or process restart, the override silently vanished and the user saw
their threshold "revert" with no surfaced error. Return HTTP 500 with
the persistence error so the frontend can show a real save-failed
toast instead of false confidence.
2026-05-28 13:15:55 +01:00
rcourtman c05eed4306 Fix TrueNAS unavailable SMART disk health
Refs #1474

Separate TrueNAS native disk state from SMART health so null or unavailable smart_status projects as UNKNOWN without replacement risk, while explicit SMART failure and native failure states still alert.

Proof:

- go test ./internal/truenas ./internal/unifiedresources ./internal/storagehealth

- npm --prefix frontend-modern test -- src/features/storageBackups/__tests__/diskPresentation.test.ts
2026-05-28 12:53:46 +01:00
rcourtman a84867c044 Expand anonymous telemetry adoption coverage 2026-05-28 11:39:30 +01:00
rcourtman cd278bf5e2 Surface alert override identities in diagnostics
Forward-port of the release/5.1 fix (9ac7df976). buildAlertsDiagnostic
previously emitted only cooldown/grouping flags, so triaging support
cases like #1341 where a user suspects an override key mismatch
required asking them to paste alerts config from inside their
container. Add an Overrides slice that names each persisted key with
its thresholds and disabled flags. Sanitize mode in the frontend
redacts the keys to override-N while keeping thresholds visible.
2026-05-28 10:56:21 +01:00
rcourtman 7cb5f74db8 Bump WebSocket read deadlines from 2s to 15s in router integration tests
The four ReadDeadline(time.Now().Add(2 * time.Second)) calls in
router_integration_test.go (lines 1496, 1543, 1573, 1682) were
producing 'read tcp: i/o timeout' failures in CI under -race while
passing locally. The 2-second window is enough to read the welcome
+ initialState messages on a quiet dev workstation but too tight
once the runner is loaded with cumulative test work and the race
detector overhead. rc.5 cleared the same tests in CI but recent
fixture-size growth (k8s clusters 1->3 in 7938f28de plus the SMART
disk-temperature mock data added in 23ea4e487) pushed the
end-to-end server-start-to-welcome-message latency past the 2s
budget. Bumping to 15s gives CI breathing room without affecting
local test duration (the deadline only takes effect when the read
is genuinely stuck).
2026-05-27 18:36:33 +01:00
rcourtman e327e09945 Fix KubernetesCluster RBAC slice race and align SECURITY.md sensor-wrapper guidance
KubernetesCluster RBAC slices were not deep-cloned

cloneKubernetesCluster cloned Nodes, Namespaces, Pods, Deployments,
and 20+ other slices via dedicated helpers but left Roles,
ClusterRoles, RoleBindings, and ClusterRoleBindings aliased to the
source slice through the dest := src shallow copy. The final
dest.NormalizeCollections() call then iterates over those four
slices and writes c.Roles[i] = c.Roles[i].NormalizeCollections()
via index assignment, which races with any concurrent clone (or
read of the same source). The race detector caught it once the
k8s cluster count was bumped from 1 to 3 in 7938f28de, which made
the contention window wide enough to hit under -race. Fix by
deep-cloning the four RBAC slices with append([]T(nil), src...)
following the same pattern as the inline slice copies elsewhere
in cloneKubernetesCluster.

SECURITY.md sensor-wrapper alignment

The SMART/SSH feature shipped in 8769f07ee updated the shipped
public security doc at frontend-modern/public/docs/SECURITY.md to
document the new Pulse-owned /usr/local/sbin/pulse-sensors wrapper
forced-command shape for the legacy SSH temperature collection
flow, but the source SECURITY.md at the repo root still described
the prior command="sensors -j" forced command. The docsLinks
test (which compares the two for byte equality) flagged the drift.
Align root SECURITY.md and re-sync the shipped copy so both
describe the wrapper contract that the setup-script and runtime
collector now own.
2026-05-27 18:13:44 +01:00
rcourtman 802b3aac49 Fix VMware inventory metric clone race and exempt docker-swarm-node from legacy-host-label assertion
Two real bugs that surfaced once the 20m test timeout let the
internal/api and internal/monitoring packages run to completion.

cloneVMwareInventoryMetrics omitted four fields:

Commit 23ea4e487 (Surface vSphere VM uptime and guest disk usage)
added UptimeSeconds, DiskUsedBytes, DiskTotalBytes, and DiskPercent
to vmware.InventoryMetrics but did not extend
cloneVMwareInventoryMetrics. The clone left those pointer fields
aliased to the source struct, so the mock fixture refresh path
(refreshVMwareInventoryMetrics writing through metrics.UptimeSeconds
via ensureInt64Ptr) and the snapshot read path
(inventoryUptimeSeconds dereferencing metrics.UptimeSeconds) raced
on the same heap-allocated int64.

TestMonitorBuildBroadcastFrontendStateUsesCanonicalMockUnifiedResources
exemption:

The test asserts broadcast state does not publish the lowercase-
hyphenated legacy docker host label so canonical docker hosts
surface their human-readable DisplayName. Commit 89abed099
(2026-05-24) added the docker-swarm-node resource type whose Name
is the swarm node hostname (matching how Docker Swarm identifies
node members), which collided with the legacy-label rejection.
Refine the assertion to apply only to host-type resources
(docker-host, agent, node).
2026-05-27 17:47:09 +01:00
rcourtman 3e61849242 Drop standalonePageModel snippet from agentless contract test; bump test timeout to 20m
TestAgentlessAvailabilityTargetKindStaysCanonical was pinning the
former agentless-machine classification in
frontend-modern/src/features/standalone/standalonePageModel.ts
(resource.availability?.targetKind,
availabilityTargetKindFor(resource) === 'machine'). Commit 1e16cf34f
intentionally narrowed the Machines surface to Pulse Agent resources
only, removing that classification, but did not update the test. The
server-side contract for availability targetKind across
config/availability.go, monitoring/availability_poller.go, types.go,
and frontend-modern/src/api/availabilityTargets.ts is preserved and
still pinned by the same test for any future consumer.

Makefile go test timeout bumped from 10m to 20m. The rc.5 backend
test run cleared 10m with slack; the rc.6 backend test run hit 13m
in internal/api before the binary panic-killed itself. 20m gives
headroom without hiding regressions for the rc.6 release path while
the package-size growth is tracked separately.
2026-05-27 17:17:13 +01:00
rcourtman 8769f07eea Land SMART/SSH temperature feature, rc.6 finalization, and post-IA-revert governance reconciliation 2026-05-27 15:27:25 +01:00
rcourtman d5589cc8ca Make agent machine onboarding seamless 2026-05-26 09:07:59 +01:00
rcourtman 7470b62a01 Fix Proxmox PBS backup artifact surface 2026-05-25 21:47:12 +01:00
rcourtman bfafe559f6 Classify agentless machine availability targets 2026-05-25 21:38:43 +01:00
rcourtman 2b3295e6f0 Speed up workload charts with TTL cache and remove redundant clones
handleWorkloadCharts: add a 3s TTL response cache + per-key singleflight
so repeated sparkline polls from dashboards share work. TestSLO_WorkloadCharts
p95 drops from 188ms (over the 90ms target) to 838µs because most polls
hit cache or coalesce. Cold-call cost is unchanged; the win is dedup of
the steady-state polling pattern.

Cache + singleflight state live on the Router struct, not as globals, so
tenants don't cross-contaminate and tests don't pollute each other. Same
treatment for stateComputeGroup added in the previous commit.

monitor_metrics.mergeMetricHistory: drop the defensive cloneMetricPointMap
of base and cloneMetricSeries of candidate inside the merge. Every caller
already passes a freshly-owned map (from filterChartMetricMap,
GetAllGuestMetrics, GetAllStorageMetrics, queryStore*), so the internal
clones were pure waste. Caller-side cloneMetricPointMap is removed in
the three sites where the input is owned. ~6% allocation reduction on
BenchmarkHandleWorkloadCharts_StoreBacked.

Contract-neutral: no endpoint, response body, header, or wire-format
change.
2026-05-25 20:35:58 +01:00
rcourtman 4721f3d1c0 Fix resolved notifications after direct alert dispatch
Refs #1350
2026-05-25 20:18:52 +01:00
rcourtman 0b57527b29 Dedup concurrent /api/state and /api/diagnostics with singleflight
Under load, 5x concurrent /api/state degraded from 276ms (single) to ~4s
each (linear), because every caller serialized on the monitor lock to
rebuild and JSON-encode the full 1.6MB state. /api/diagnostics had the
same dogpile shape on cache miss, even though its 45s TTL cache was
working as designed.

Wrap both handlers in a per-tenant singleflight.Group so concurrent
callers share the work: 20x concurrent /api/state now completes in 358ms
wall (~14-50x improvement). Diagnostics warm-cache responses are now
sub-3ms; cold compute coalesces.

Also drop websocket Upgrader buffers from 4MB read/write to 64KB. gorilla
streams larger payloads across the buffer transparently, so the 4MB
allocation per connection was overhead that scaled badly with concurrent
clients (100 clients * 8MB = 800MB just in buffers).

Contract-neutral: no endpoint, response body, header, or wire-format
change.
2026-05-25 20:07:02 +01:00
rcourtman b6af1d233e Wire platform poller state into connections ledger
Refs #1469
2026-05-25 18:20:11 +01:00
rcourtman c82817c099 Fix Discovery response JSON extraction
Refs #1479
2026-05-25 18:02:28 +01:00
rcourtman 052e344e1b Add Kubernetes RBAC inventory to the agent + canonical + UI
Closes the only API-coverage gap from the Docker / Kubernetes IA
maturity review: Roles, ClusterRoles, RoleBindings, and
ClusterRoleBindings now flow from the Kubernetes agent through the
canonical resource registry into the Kubernetes platform-page
Configuration tab.

Agent: pkg/agents/kubernetes/report.go gains four new report struct
types that carry summary counts plus subject-kind sets; individual
subject names and full PolicyRule contents are deliberately omitted
so Pulse stays a "what permissions exist where" surface, not an RBAC
enumeration tool. internal/kubernetesagent/agent.go gains four
collectors that call rbacv1.RoleList/ClusterRoleList/etc. through the
existing runKubernetesCallWithRetry wrapper, matching the
ServiceAccount collector's RBAC-forbidden retry pattern.

Canonical: internal/models mirrors with NormalizeCollections coverage;
convert* funcs in internal/monitoring/kubernetes_agents.go translate
agent report -> model; ResourceTypeK8sRole / K8sClusterRole /
K8sRoleBinding / K8sClusterRoleBinding join the canonical type set;
registry ingest* + adapter resourceFrom* functions emit one Resource
per RBAC object with ruleCount / roleKind / roleName / subjectCount /
subjectKinds / aggregationLabels on the K8s meta; search mapping in
internal/api/resources.go and the privacy allow-list in
internal/api/org_handlers.go pick up the four new type tokens; the
K8s privacy category in unifiedresources/policy_metadata.go classifies
them like the rest of K8s.

Frontend: ResourceType union + ResourceKubernetesMeta carry the new
kinds and RBAC summary fields; KubernetesPageSurface query asks for
them; the page model buckets them into the Configuration group;
KubernetesConfigTable renders Role / ClusterRole rule counts and the
aggregated flag, plus RoleBinding / ClusterRoleBinding role refs and
"N subjects · Kind1, Kind2 +overflow" subject summaries.

Curated demo seeds per-namespace Roles + RoleBindings plus an
aggregated ClusterRole + ClusterRoleBinding for pulse-demo-monitoring
in each cluster so the Configuration tab renders 18 RBAC rows across
the three demo clusters.

Contracts updated for the canonical-shape guard: monitoring,
api-contracts, unified-resources, frontend-primitives,
organization-settings (canonical) plus agent-lifecycle and
storage-recovery (dependent via Extension Points). Verification
proofs extended: kubernetes_registry_test.go, kubernetes_agents_test.go,
agent_inventory_test.go (new TestCollectRBACInventoryReportsSummaryCountsOnly
that pins the subject-name-omission contract), demo_scenarios_test.go,
adapter_coverage_test.go, contract_test.go, org_handlers_test.go,
resourceIdentity.test.ts, reportingResourceTypes.test.ts,
KubernetesConfigTable.test.tsx, and the
subsystem_lookup_test.py line-anchor bumps that the contract edits
shifted (api-contracts 246 -> 253, organization-settings 92 -> 93).

Verified:
- go build ./internal/... ./cmd/... clean
- go test ./internal/unifiedresources/..., ./internal/mock/...,
  ./internal/kubernetesagent/..., ./internal/api/...,
  the K8s subset of ./internal/monitoring/... all clean (three
  pre-existing unrelated monitoring failures noted earlier remain
  unchanged by this commit)
- npm run type-check, lint:eslint, lint:theme,
  lint:canonical-platforms clean
- vitest: 70 K8s frontend tests pass including the new RBAC render
  coverage in KubernetesConfigTable.test.tsx
- browser proof on /kubernetes/configuration: 36 config rows
  including 18 RBAC rows across three clusters; ClusterRole
  "pulse-demo-monitoring" shows "12 rules · Aggregated";
  ClusterRoleBinding shows "3 subjects · Group, ServiceAccount +1"
2026-05-25 09:25:03 +01:00
rcourtman 29b5a9cc00 Give each curated Kubernetes demo cluster a distinct story
Per-cluster node profiles, kubelet versions, and degraded scenarios
replace the global rotation that made every demo cluster look like a
copy of the same one. Production EU keeps its prod-euw1-k8s-{01..05}
nodes and the NotReady worker on prod-euw1-k8s-03 (preserving the
existing host-posture test contract). Staging EU runs
stage-euw1-k8s-{01..05} and carries the payments-worker
CrashLoopBackOff. Development EU runs dev-euw1-{01..05} and carries
an ImagePullBackOff on cron-nightly-backfill (re-labelled from the
previous "Pending / PodInitializing" rotation so the curated
reconciler doesn't recover it). The unused Edge profile gets distinct
edge-pop-{lax,nrt,fra,iad,sin}-01 names + k3s version for when the
cluster count is bumped above three.

A new TestKubernetesDemoClustersTellDistinctStories test guards the
slice goal: each cluster's nodes use its own prefix, exactly one
cluster carries each degraded scenario, and every cluster has a
unique kubelet version. The monitoring subsystem contract is updated
to reflect the new three-cluster cast (Production EU + Staging EU +
Development EU) plus the per-cluster scenario distribution.

Side effect: with distinct node names per cluster, the K8s page
model's cluster-to-node matching now resolves all five nodes for
each cluster (previously two clusters showed "0 nodes" because every
cluster's nodes shared the same prod-euw1-* names, breaking
buildKubernetesClusterChildCounts' clusterId lookup).

Verified:
- go vet ./internal/mock/..., go test ./internal/mock/... clean
- browser proof on /kubernetes/overview: three clusters render with
  distinct versions (v1.30.4 / v1.31.2 / v1.32.0-rc.1) and 5 nodes
  each (vs the previous 5/0/0 split)
- /kubernetes/nodes: 15 rows across the three clusters with three
  distinct name prefixes; one red NotReady dot on Production EU's
  prod-euw1-k8s-03; fourteen green Ready dots elsewhere
2026-05-25 08:28:20 +01:00
rcourtman 6d7ee5d732 Tighten platform overview IA 2026-05-24 19:41:29 +01:00
rcourtman a590024ca0 Consolidate container and Kubernetes platform tabs 2026-05-24 19:20:14 +01:00
rcourtman 3403104662 Expose Docker and Kubernetes API tab fields 2026-05-24 18:47:55 +01:00
rcourtman 223b11185f Add native Docker containers table 2026-05-24 17:41:35 +01:00
rcourtman f59ec0ceaf Add Kubernetes controllers native table 2026-05-24 16:38:49 +01:00
rcourtman aa14a96644 Add Kubernetes services native table 2026-05-24 14:34:13 +01:00
rcourtman ecd3e4d377 Add Kubernetes networking native table 2026-05-24 14:21:25 +01:00
rcourtman c8380613d7 Seed native platform tab fixtures 2026-05-24 13:16:00 +01:00
rcourtman 49c9ca7656 Use metadata-only Kubernetes config inventory 2026-05-24 12:36:50 +01:00
rcourtman 120dd5353a Expand Docker Swarm metadata inventory 2026-05-24 12:07:10 +01:00
rcourtman 0d67ca1b4a Expand Kubernetes API-native inventory surfaces
Collect native Kubernetes config, policy, and autoscaling objects.
Project the new resource types through API filters, unified resources, mock fixtures, and Kubernetes tabs.
Keep Secret inventory metadata-only and route k8s-secret policy as restricted local-only.
2026-05-24 11:12:33 +01:00
rcourtman 89abed099c Expand Docker runtime inventory coverage 2026-05-24 10:24:42 +01:00
rcourtman f18502fc24 Expand Kubernetes native inventory coverage 2026-05-24 09:40:58 +01:00
rcourtman 6346929328 Expand Docker and Kubernetes platform projections 2026-05-24 08:58:02 +01:00
rcourtman d3934e19e3 Harden repository advisory boundaries 2026-05-24 08:15:29 +01:00
rcourtman 78d124a392 Align Agents page with platform machine tables 2026-05-23 17:22:42 +01:00
rcourtman a57217b194 Add Version + Uptime columns to vSphere Hosts table
VsphereHostsTable was missing the Version (ESXi build) and Uptime
columns that Proxmox / Docker / Kubernetes / TrueNAS host
equivalents carry. The data was already piped through the
canonical projection — ESXi version on resource.agent.osVersion
(e.g. "8.0.3"), host uptime on resource.uptime (lifted from
InventoryMetrics.UptimeSeconds via the sys.uptime.latest
PerformanceManager counter wired in 23ea4e487 this morning) —
the table just didn't render columns for them.

VsphereHostsTable now renders Version and Uptime cells, ordered
Version-adjacent-to-Host and Uptime before vCenter to mirror the
Proxmox Nodes layout. Column widths trimmed to fit. Uptime uses
the shared formatUptime util in condensed form (e.g. "163d")
with the full label as the cell title attribute. Detail row
colspan bumps 9 to 11 to keep the drawer spanning the full row.

Plumbing the canonical Resource.Uptime through to the frontend
required two fallback extensions surfaced by the column audit:

- useUnifiedResources.ts toResource: the uptime fallback chain
  ended on platform-specific carve-outs (agent.uptimeSeconds,
  proxmox.uptime, pbs/pmg/kubernetes); vSphere populates only
  the canonical Resource.Uptime, so the chain has to land on
  v2.uptime. Same shape as the workloads-hook fix in e5b31f484.
- internal/monitoring/monitor.go monitorUptime: the websocket
  broadcast converter walked the same platform-specific chain
  and silently dropped vSphere host/VM uptime, then merge-
  clobbered the REST-loaded value once WS reconnect replayed
  the broadcast. Add the canonical resource.Uptime fallback so
  the broadcast payload carries uptime for VMware-backed rows
  consistently with the REST contract. Carve-outs still take
  precedence so existing platforms keep prior behavior.

Verified: vSphere overview Hosts table now renders esxi-01..07
with Version 8.0.3 and Uptime 147d-167d cells. No regression in
Proxmox / Docker / Kubernetes / TrueNAS uptime paths.

Contracts:
- monitoring.md documents the canonical Resource.Uptime fallback
  in monitorUptime and the carve-out precedence.
- unified-resources.md adds the same canonical-uptime fallback
  rule to the toResource consumer-side contract.
- storage-recovery.md amends rule 29 to call out that the
  canonical Resource.Uptime fallback is descriptive host/VM
  uptime only and must not be reinterpreted as backup recency
  or recovery cadence.

Proofs:
- internal/monitoring/canonical_guardrails_test.go locks the
  monitorUptime fallback contract: canonical Resource.Uptime is
  surfaced when no carve-out is set, carve-outs take precedence,
  nil when nothing populates.
- frontend-modern/src/hooks/__tests__/useUnifiedResources.test.ts
  asserts the toResource v2.uptime fallback for a vSphere-shaped
  payload with no platform-specific uptime carve-out.
2026-05-23 15:11:49 +01:00
rcourtman 23ea4e4872 Surface vSphere VM uptime and guest disk usage
The vSphere adapter's InventoryMetrics struct only carried
throughput / utilisation metrics. Uptime and guest filesystem
usage weren't piped through at all, so the workloads table
rendered "0s" and empty cells for every vSphere VM.

Backend (internal/vmware):
- InventoryMetrics gains UptimeSeconds plus DiskUsedBytes /
  DiskTotalBytes / DiskPercent. Documented in the struct comment
  with the API sources they come from.
- PerformanceManager counter catalog adds sys.uptime.latest for
  hosts and VMs and sys.osUptime.latest for VMs. The mapping
  prefers guest OS uptime when present (Tools-reported) and falls
  back to VMX-process uptime. Counters verified against vSphere 8
  developer documentation.
- New per-VM REST collector calls
  GET /api/vcenter/vm/{vm}/guest/local-filesystem and aggregates
  per-mount capacity / free_space into DiskTotal / DiskUsed /
  DiskPercent. A 503 from vCenter (Tools not reporting) is
  classified as a non-fatal enrichment issue and the row stays
  blank rather than failing the collection.
- enrichInventorySnapshot now takes automationSessionID so the
  signals path can hit the REST endpoint alongside the VI/JSON
  PerformanceManager queries.
- Resource projection layer wires UptimeSeconds onto
  Resource.Uptime for hosts and VMs and the disk fields onto
  metrics.disk; cloneInventoryMetrics tracks the new pointers.

Mock (internal/mock):
- refreshVMwareInventoryMetrics synthesizes plausible per-resource
  uptime (1h - 30d base, climbing forward with snapshot time) and,
  for VMs only, a stable guest filesystem total (32-256 GiB) with
  naturally-oscillating used bytes via SampleMetric. Powered-off
  VMs drop the new pointers so the frontend renders "-" rather
  than zero, matching how the canonical "no data" signal already
  works for offline guests.

Frontend (useWorkloads.ts):
- The WorkloadGuest uptime fallback chain now lands on the
  canonical resource.uptime field. vSphere doesn't populate a
  platform-specific carve-out (only the canonical field), so the
  earlier proxmox/agent/docker/kubernetes-only chain was silently
  dropping vSphere uptime.

Contracts:
- monitoring.md documents the new InventoryMetrics fields, their
  vSphere collection sources, and the mock-fixture expectation.
- performance-and-scalability.md adds the canonical
  resource.uptime fallback rule to the workload mapping section.

Proofs:
- internal/mock/platform_fixtures_test.go asserts that powered-on
  vSphere VMs surface uptime + guest disk fields and powered-off
  VMs drop them.
- frontend-modern/src/hooks/__tests__/useWorkloads.test.ts adds a
  vSphere uptime fallback case.
- Existing vmware client test
  (TestClientCollectInventoryPreservesBaseInventoryWhenOptionalEnrichmentDegrades)
  teaches the mock vCenter to serve the new endpoint and updates
  the assertions to match the additional non-fatal issue surfaced
  when the unavailableVMGuestInfo knob also degrades the
  filesystem read.
2026-05-23 10:07:21 +01:00
rcourtman 5016cbc2ba Add vSphere network inventory
Project vCenter network inventory through canonical resources and add the vSphere Networks table backed by vCenter network topology. Align resource presentation coalescing so state and resource APIs share the same host contract.
2026-05-22 20:26:56 +01:00