From 8ae422c243c3229d95b6fbd7eb7157a3e9af1e5f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 19 May 2026 22:18:29 +0100 Subject: [PATCH] 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). --- .../kubernetes/KubernetesPageSurface.tsx | 22 +++++++++-- .../features/proxmox/ProxmoxPageSurface.tsx | 8 +++- .../features/truenas/TrueNASPageSurface.tsx | 8 +++- .../src/features/vmware/VmwarePageSurface.tsx | 8 +++- .../src/utils/resourceSearchMatch.ts | 37 +++++++++++++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 frontend-modern/src/utils/resourceSearchMatch.ts diff --git a/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx b/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx index 3a0c64d851..273d9edd2f 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx @@ -5,6 +5,7 @@ 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 { resourceMatchesSearch } from '@/utils/resourceSearchMatch'; import { PlatformErrorState, PlatformSectionTabs, @@ -116,6 +117,21 @@ function KubernetesOverview(props: KubernetesOverviewProps) { workloadsState.surfaceInitialDataReceived() && workloadsState.allGuests().length > 0, ); + const searchTerm = createMemo(() => workloadsState.search().trim()); + const filteredClusters = createMemo(() => { + if (!searchTerm()) return props.model().clusters; + return props.model().clusters.filter((cluster) => resourceMatchesSearch(cluster, searchTerm())); + }); + const filteredNodes = createMemo(() => { + if (!searchTerm()) return props.model().nodes; + return props.model().nodes.filter((node) => resourceMatchesSearch(node, searchTerm())); + }); + const filteredDeployments = createMemo(() => { + if (!searchTerm()) return props.model().deployments; + return props.model().deployments.filter((deployment) => + resourceMatchesSearch(deployment, searchTerm()), + ); + }); return (
@@ -159,7 +175,7 @@ function KubernetesOverview(props: KubernetesOverviewProps) {
0}> 0, ); + const filteredNodes = createMemo(() => { + const term = workloadsState.search(); + if (!term.trim()) return props.model().pveNodes; + return props.model().pveNodes.filter((node) => resourceMatchesSearch(node, term)); + }); return (
@@ -262,7 +268,7 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) {
0, ); + const filteredSystems = createMemo(() => { + const term = workloadsState.search().trim(); + if (!term) return props.model().systems; + return props.model().systems.filter((system) => resourceMatchesSearch(system, term)); + }); return (
@@ -178,7 +184,7 @@ function TrueNASOverview(props: TrueNASOverviewProps) {
0, ); + const filteredHosts = createMemo(() => { + const term = workloadsState.search().trim(); + if (!term) return props.model().hosts; + return props.model().hosts.filter((host) => resourceMatchesSearch(host, term)); + }); return (
@@ -176,7 +182,7 @@ function VmwareOverview(props: VmwareOverviewProps) {
{ + const raw: Array = [ + resource.id, + resource.name, + resource.displayName, + resource.parentName, + resource.agent?.hostname, + resource.identity?.hostname, + resource.canonicalIdentity?.displayName, + resource.canonicalIdentity?.hostname, + resource.canonicalIdentity?.primaryId, + resource.docker?.hostname, + resource.kubernetes?.clusterName, + resource.kubernetes?.nodeName, + resource.kubernetes?.namespace, + resource.vmware?.runtimeHostName, + resource.vmware?.clusterName, + resource.vmware?.datacenterName, + resource.proxmox?.node, + resource.proxmox?.nodeName, + resource.proxmox?.clusterName, + ...(resource.canonicalIdentity?.aliases ?? []), + ...(resource.tags ?? []), + ]; + return raw + .filter((value): value is string => typeof value === 'string') + .map((value) => value.trim().toLowerCase()) + .filter((value) => value.length > 0); +}; + +export function resourceMatchesSearch(resource: Resource, term: string | null | undefined): boolean { + const needle = (term ?? '').trim().toLowerCase(); + if (!needle) return true; + return collectSearchCandidates(resource).some((value) => value.includes(needle)); +}