From 58b2e10c1f311bad9b016c40645adb917e1476ea Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 19 May 2026 21:45:40 +0100 Subject: [PATCH] Hoist workload filter to top on platform overview pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the WorkloadsFilter to the top of the Proxmox, Kubernetes, TrueNAS, and vSphere overview surfaces so every workload page renders the filter bar above its tables, matching the Docker pattern. The mid-page filter placement was inconsistent across pages and pushed the search input below the fold on busy hosts/nodes/clusters tables. Each page lifts useWorkloadsState to its overview component, renders WorkloadsFilter directly, and passes the state into WorkloadsSurface (removing showFilterToolbar and the filter* configuration props from the surface). Filter scope is unchanged — it still governs the embedded workloads table only. --- .../kubernetes/KubernetesPageSurface.tsx | 164 +++++++++++++----- .../features/proxmox/ProxmoxPageSurface.tsx | 150 ++++++++++++---- .../features/truenas/TrueNASPageSurface.tsx | 130 ++++++++++---- .../src/features/vmware/VmwarePageSurface.tsx | 131 ++++++++++---- 4 files changed, 430 insertions(+), 145 deletions(-) diff --git a/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx b/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx index 74993fdd7b..3a0c64d851 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx @@ -1,6 +1,8 @@ import ShipWheelIcon from 'lucide-solid/icons/ship-wheel'; -import { Show, createMemo } from 'solid-js'; +import { Show, createMemo, type Accessor } from 'solid-js'; +import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter'; import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface'; +import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState'; import type { WorkloadsStatusOption } from '@/components/Workloads/workloadsFilterModel'; import { useUnifiedResources } from '@/hooks/useUnifiedResources'; import { @@ -11,7 +13,11 @@ import { import { KubernetesClustersTable } from './KubernetesClustersTable'; import { KubernetesDeploymentsTable } from './KubernetesDeploymentsTable'; import { KubernetesNodesTable } from './KubernetesNodesTable'; -import { KUBERNETES_TAB_SPECS, buildKubernetesPageModel } from './kubernetesPageModel'; +import { + KUBERNETES_TAB_SPECS, + buildKubernetesPageModel, + type KubernetesPageModel, +} from './kubernetesPageModel'; // Include `agent` rows so K8s nodes that the backend registry merged onto // the linked agent host (sources=['agent','kubernetes']) still appear in the @@ -76,51 +82,7 @@ export function KubernetesPageSurface() { /> } > -
- - - - 0}> - - -
+ @@ -128,4 +90,112 @@ export function KubernetesPageSurface() { ); } +interface KubernetesOverviewProps { + model: Accessor; +} + +function KubernetesOverview(props: KubernetesOverviewProps) { + const workloadsState = useWorkloadsState({ + vms: [], + containers: [], + nodes: [], + useWorkloads: true, + embedded: true, + tableOnly: true, + forcedPlatform: KUBERNETES_PLATFORM_FILTER, + forcedViewMode: KUBERNETES_WORKLOAD_FORCED_VIEW_MODE, + showFilterToolbar: true, + suppressPlatformFilter: true, + allowEmbeddedScopeFilters: true, + columnVisibilityStorageScope: KUBERNETES_WORKLOAD_COLUMN_SCOPE, + compactGroupHeaders: true, + }); + const showSharedFilterToolbar = createMemo( + () => + workloadsState.surfaceConnected() && + workloadsState.surfaceInitialDataReceived() && + workloadsState.allGuests().length > 0, + ); + + return ( +
+ +
+ + Boolean( + workloadsState.selectedGuestId() || + workloadsState.focusedSummaryWorkloadGroupId(), + ) + } + onClearPinnedSelection={workloadsState.clearPinnedSummaryScope} + /> +
+
+ + + + 0}> + + +
+ ); +} + export default KubernetesPageSurface; diff --git a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx index 6a4f340b08..b966eb0f80 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx @@ -1,7 +1,9 @@ import { useLocation } from '@solidjs/router'; -import { Show, createMemo } from 'solid-js'; +import { Show, createMemo, type Accessor } from 'solid-js'; import StorageSurface from '@/components/Storage/Storage'; +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, @@ -30,6 +32,7 @@ import { PROXMOX_TAB_SPECS, buildProxmoxPageModel, buildVisibleProxmoxTabSpecs, + type ProxmoxPageModel, type ProxmoxPageTabId, } from './proxmoxPageModel'; @@ -132,42 +135,13 @@ export function ProxmoxPageSurface() { } > -
- } - emptyTitle="No Proxmox VE nodes" - emptyDescription="Proxmox VE nodes appear here once a PVE host reports inventory." - /> - -
+
; + metricDisplayMode: Accessor; + setMetricDisplayMode: (value: WorkloadsMetricDisplayMode) => void; + metricHistoryRange: Accessor; + setMetricHistoryRange: (value: WorkloadTableMetricHistoryRange) => void; +} + +function ProxmoxOverview(props: ProxmoxOverviewProps) { + const workloadsState = useWorkloadsState({ + vms: [], + containers: [], + nodes: [], + useWorkloads: true, + embedded: true, + tableOnly: true, + forcedPlatform: PROXMOX_PLATFORM_FILTER, + showFilterToolbar: true, + suppressPlatformFilter: true, + allowEmbeddedScopeFilters: true, + statusModeStorageScope: PROXMOX_WORKLOAD_STATUS_STORAGE_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} + /> +
+
+ } + emptyTitle="No Proxmox VE nodes" + emptyDescription="Proxmox VE nodes appear here once a PVE host reports inventory." + /> + +
+ ); +} + export default ProxmoxPageSurface; diff --git a/frontend-modern/src/features/truenas/TrueNASPageSurface.tsx b/frontend-modern/src/features/truenas/TrueNASPageSurface.tsx index 85ad73ef06..9a9f11296b 100644 --- a/frontend-modern/src/features/truenas/TrueNASPageSurface.tsx +++ b/frontend-modern/src/features/truenas/TrueNASPageSurface.tsx @@ -1,8 +1,10 @@ import { useLocation } from '@solidjs/router'; import DatabaseIcon from 'lucide-solid/icons/database'; -import { Show, createMemo } from 'solid-js'; +import { Show, createMemo, type Accessor } from 'solid-js'; import StorageSurface from '@/components/Storage/Storage'; +import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter'; import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface'; +import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState'; import type { WorkloadsStatusOption } from '@/components/Workloads/workloadsFilterModel'; import { useUnifiedResources } from '@/hooks/useUnifiedResources'; import { @@ -14,6 +16,7 @@ import { TrueNASSystemsTable } from './TrueNASSystemsTable'; import { TRUENAS_TAB_SPECS, buildTrueNASPageModel, + type TrueNASPageModel, type TrueNASPageTabId, } from './truenasPageModel'; @@ -86,36 +89,7 @@ export function TrueNASPageSurface() { } > -
- - 0}> - - -
+
; +} + +function TrueNASOverview(props: TrueNASOverviewProps) { + const workloadsState = useWorkloadsState({ + vms: [], + containers: [], + nodes: [], + useWorkloads: true, + embedded: true, + tableOnly: true, + forcedPlatform: TRUENAS_PLATFORM_FILTER, + forcedViewMode: 'app-container', + showFilterToolbar: true, + suppressPlatformFilter: true, + allowEmbeddedScopeFilters: true, + compactGroupHeaders: true, + }); + const showSharedFilterToolbar = createMemo( + () => + props.model().apps.length > 0 && + workloadsState.surfaceConnected() && + workloadsState.surfaceInitialDataReceived() && + workloadsState.allGuests().length > 0, + ); + + return ( +
+ +
+ + Boolean( + workloadsState.selectedGuestId() || + workloadsState.focusedSummaryWorkloadGroupId(), + ) + } + onClearPinnedSelection={workloadsState.clearPinnedSummaryScope} + /> +
+
+ + 0}> + + +
+ ); +} + export default TrueNASPageSurface; diff --git a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx index 7afa7c8fa2..63eb4ec2e5 100644 --- a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx +++ b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx @@ -1,8 +1,10 @@ import { useLocation } from '@solidjs/router'; import CpuIcon from 'lucide-solid/icons/cpu'; -import { Show, createMemo } from 'solid-js'; +import { Show, createMemo, type Accessor } from 'solid-js'; import StorageSurface from '@/components/Storage/Storage'; +import { WorkloadsFilter } from '@/components/Workloads/WorkloadsFilter'; import { WorkloadsSurface } from '@/components/Workloads/WorkloadsSurface'; +import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState'; import type { WorkloadsStatusOption } from '@/components/Workloads/workloadsFilterModel'; import { useUnifiedResources } from '@/hooks/useUnifiedResources'; import { @@ -11,7 +13,12 @@ import { PlatformTableEmptyState, } from '@/features/platformPage/sharedPlatformPage'; import { VsphereHostsTable } from './VsphereHostsTable'; -import { VMWARE_TAB_SPECS, buildVmwarePageModel, type VmwarePageTabId } from './vmwarePageModel'; +import { + VMWARE_TAB_SPECS, + buildVmwarePageModel, + type VmwarePageModel, + type VmwarePageTabId, +} from './vmwarePageModel'; // `datastore` is not a first-class type token at the API boundary; vSphere // datastores are emitted as canonical `storage` rows. Including it @@ -80,34 +87,7 @@ export function VmwarePageSurface() { } > -
- - -
+
; +} + +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, + compactGroupHeaders: true, + }); + const showSharedFilterToolbar = createMemo( + () => + workloadsState.surfaceConnected() && + workloadsState.surfaceInitialDataReceived() && + workloadsState.allGuests().length > 0, + ); + + return ( +
+ +
+ + Boolean( + workloadsState.selectedGuestId() || + workloadsState.focusedSummaryWorkloadGroupId(), + ) + } + onClearPinnedSelection={workloadsState.clearPinnedSummaryScope} + /> +
+
+ + +
+ ); +} + export default VmwarePageSurface;