From d96d5612fd14b2467b2cce44b73e2eb5c5e28434 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 23 May 2026 08:43:01 +0100 Subject: [PATCH] Route vSphere VMs through WorkloadsSurface The vSphere overview's VM table was a bespoke component that parallel-implemented what Proxmox, Docker, and Kubernetes already get from the shared WorkloadsSurface. The result was an overview page that lacked the Grouped/List, Bars/Trends/Columns, and metric history toggles operators expect on a Pulse platform page. Wire VmwareOverview to useWorkloadsState + WorkloadsFilter + WorkloadsSurface using the same embedded shape Proxmox uses: forcedPlatform='vmware-vsphere', forcedViewMode='vm', shared metricDisplayMode and metricHistoryRange persistent signals, storage-scoped status and column visibility, compactGroupHeaders so VsphereHostsTable keeps its dedicated layout above. The existing power-state vocabulary maps cleanly: normalizeWorkloadStatus already collapses online->running and offline->stopped, and vSphere resource IDs (vm-XXXX hashes) match the workload chart key candidates so sparkline mode resolves without extra plumbing. VsphereVirtualMachinesTable stays in the tree for now; deletion lands once Phase 1 is observed stable. --- .../src/features/vmware/VmwarePageSurface.tsx | 145 +++++++++++++++++- 1 file changed, 137 insertions(+), 8 deletions(-) diff --git a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx index 69d63aa17b..ea574a34f7 100644 --- a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx +++ b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx @@ -9,6 +9,21 @@ import { PlatformTableEmptyState, PlatformTableLoadingState, } from '@/features/platformPage/sharedPlatformPage'; +import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter'; +import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface'; +import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState'; +import { + DEFAULT_WORKLOADS_METRIC_DISPLAY_MODE, + type WorkloadsStatusOption, + type WorkloadsMetricDisplayMode, +} from '@/components/Workloads/workloadsFilterModel'; +import { + WORKLOAD_TABLE_HISTORY_DEFAULT_RANGE, + isWorkloadTableMetricHistoryRange, + type WorkloadTableMetricHistoryRange, +} from '@/components/Workloads/workloadMetricHistoryModel'; +import { usePersistentSignal } from '@/hooks/usePersistentSignal'; +import { STORAGE_KEYS } from '@/utils/localStorage'; import { VsphereHostsTable } from './VsphereHostsTable'; import { VMWARE_TAB_SPECS, @@ -20,7 +35,6 @@ import { VsphereAlertsTable } from './VsphereAlertsTable'; import { VsphereActivityTable } from './VsphereActivityTable'; import { VsphereDatastoresTable } from './VsphereDatastoresTable'; import { VsphereNetworksTable } from './VsphereNetworksTable'; -import { VsphereVirtualMachinesTable } from './VsphereVirtualMachinesTable'; // vSphere phase 1 projects ESXi hosts as canonical `agent`, virtual machines // as canonical `vm`, datastores as canonical `storage`, and vCenter networks @@ -29,6 +43,16 @@ import { VsphereVirtualMachinesTable } from './VsphereVirtualMachinesTable'; const VMWARE_RESOURCE_QUERY = 'type=agent,vm,storage,network'; const VALID_TABS = new Set(VMWARE_TAB_SPECS.map((tab) => tab.id)); +const VMWARE_PLATFORM_FILTER = 'vmware-vsphere'; +const VMWARE_WORKLOAD_STATUS_STORAGE_SCOPE = 'vmware'; +const VMWARE_WORKLOAD_COLUMN_VISIBILITY_SCOPE = 'vmware-vms'; +const VMWARE_WORKLOAD_STATUS_OPTIONS: readonly WorkloadsStatusOption[] = [ + { value: 'all', label: 'All' }, + { value: 'running', label: 'Powered on' }, + { value: 'degraded', label: 'Attention' }, + { value: 'stopped', label: 'Powered off' }, +]; + const vmwareIcon = () => ; export function VmwarePageSurface() { @@ -56,6 +80,28 @@ export function VmwarePageSurface() { ); const model = createMemo(() => buildVmwarePageModel(resources(), activityTimeline() ?? [])); + // Hosts table on top and the embedded WorkloadsSurface below share the + // bars/sparklines toggle (and the sparkline history range that ships with + // it). Owning the persistent signals at the page level keeps the same shape + // Proxmox uses, so the in-toolbar segmented control drives both. + const [metricDisplayMode, setMetricDisplayMode] = usePersistentSignal( + STORAGE_KEYS.WORKLOADS_METRIC_DISPLAY_MODE, + DEFAULT_WORKLOADS_METRIC_DISPLAY_MODE, + { + deserialize: (raw) => + raw === 'bars' || raw === 'sparklines' ? raw : DEFAULT_WORKLOADS_METRIC_DISPLAY_MODE, + }, + ); + const [metricHistoryRange, setMetricHistoryRange] = + usePersistentSignal( + STORAGE_KEYS.WORKLOADS_METRIC_HISTORY_RANGE, + WORKLOAD_TABLE_HISTORY_DEFAULT_RANGE, + { + deserialize: (raw) => + isWorkloadTableMetricHistoryRange(raw) ? raw : WORKLOAD_TABLE_HISTORY_DEFAULT_RANGE, + }, + ); + return (
- + ; + metricDisplayMode: Accessor; + setMetricDisplayMode: (value: WorkloadsMetricDisplayMode) => void; + metricHistoryRange: Accessor; + setMetricHistoryRange: (value: WorkloadTableMetricHistoryRange) => void; } function VmwareOverview(props: VmwareOverviewProps) { + const workloadsState = useWorkloadsState({ + vms: [], + containers: [], + nodes: [], + useWorkloads: true, + embedded: true, + tableOnly: true, + forcedPlatform: VMWARE_PLATFORM_FILTER, + forcedViewMode: 'vm', + showFilterToolbar: true, + suppressPlatformFilter: true, + allowEmbeddedScopeFilters: true, + statusModeStorageScope: VMWARE_WORKLOAD_STATUS_STORAGE_SCOPE, + columnVisibilityStorageScope: VMWARE_WORKLOAD_COLUMN_VISIBILITY_SCOPE, + compactGroupHeaders: true, + groupNodeDrawerMode: 'disabled', + metricDisplayMode: props.metricDisplayMode, + onMetricDisplayModeChange: props.setMetricDisplayMode, + metricHistoryRange: props.metricHistoryRange, + onMetricHistoryRangeChange: props.setMetricHistoryRange, + }); + const showSharedFilterToolbar = createMemo( + () => + workloadsState.surfaceConnected() && + workloadsState.surfaceInitialDataReceived() && + workloadsState.allGuests().length > 0, + ); + return (
- +
+ + Boolean( + workloadsState.selectedGuestId() || workloadsState.focusedSummaryWorkloadGroupId(), + ) + } + onClearPinnedSelection={workloadsState.clearPinnedSummaryScope} + /> +
+ +
);