mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 16:42:15 +00:00
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).
This commit is contained in:
@@ -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 (
|
||||
<div class="space-y-4">
|
||||
@@ -159,7 +175,7 @@ function KubernetesOverview(props: KubernetesOverviewProps) {
|
||||
</div>
|
||||
</Show>
|
||||
<KubernetesClustersTable
|
||||
clusters={props.model().clusters}
|
||||
clusters={filteredClusters()}
|
||||
scope={props.model().resources}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No clusters reported"
|
||||
@@ -167,7 +183,7 @@ function KubernetesOverview(props: KubernetesOverviewProps) {
|
||||
showToolbar={false}
|
||||
/>
|
||||
<KubernetesNodesTable
|
||||
resources={props.model().nodes}
|
||||
resources={filteredNodes()}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No nodes reported"
|
||||
emptyDescription="Kubernetes nodes appear here as soon as the agent enumerates them."
|
||||
@@ -187,7 +203,7 @@ function KubernetesOverview(props: KubernetesOverviewProps) {
|
||||
/>
|
||||
<Show when={props.model().deployments.length > 0}>
|
||||
<KubernetesDeploymentsTable
|
||||
resources={props.model().deployments}
|
||||
resources={filteredDeployments()}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No deployments reported"
|
||||
emptyDescription="Deployments appear here once the cluster reports them."
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { ProxmoxIcon } from '@/components/icons/ProxmoxIcon';
|
||||
import { usePersistentSignal } from '@/hooks/usePersistentSignal';
|
||||
import { STORAGE_KEYS } from '@/utils/localStorage';
|
||||
import { resourceMatchesSearch } from '@/utils/resourceSearchMatch';
|
||||
import {
|
||||
PlatformErrorState,
|
||||
PlatformSectionTabs,
|
||||
@@ -220,6 +221,11 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) {
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 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 (
|
||||
<div class="space-y-4">
|
||||
@@ -262,7 +268,7 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) {
|
||||
</div>
|
||||
</Show>
|
||||
<ProxmoxNodesTable
|
||||
nodes={props.model().pveNodes}
|
||||
nodes={filteredNodes()}
|
||||
guests={props.model().guests}
|
||||
metricDisplayMode={props.metricDisplayMode}
|
||||
metricHistoryRange={props.metricHistoryRange}
|
||||
|
||||
@@ -7,6 +7,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,
|
||||
@@ -135,6 +136,11 @@ function TrueNASOverview(props: TrueNASOverviewProps) {
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 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 (
|
||||
<div class="space-y-4">
|
||||
@@ -178,7 +184,7 @@ function TrueNASOverview(props: TrueNASOverviewProps) {
|
||||
</div>
|
||||
</Show>
|
||||
<TrueNASSystemsTable
|
||||
systems={props.model().systems}
|
||||
systems={filteredSystems()}
|
||||
scope={props.model().resources}
|
||||
emptyIcon={truenasIcon()}
|
||||
emptyTitle="No TrueNAS systems"
|
||||
|
||||
@@ -7,6 +7,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,
|
||||
@@ -133,6 +134,11 @@ function VmwareOverview(props: VmwareOverviewProps) {
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 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 (
|
||||
<div class="space-y-4">
|
||||
@@ -176,7 +182,7 @@ function VmwareOverview(props: VmwareOverviewProps) {
|
||||
</div>
|
||||
</Show>
|
||||
<VsphereHostsTable
|
||||
hosts={props.model().hosts}
|
||||
hosts={filteredHosts()}
|
||||
scope={props.model().resources}
|
||||
emptyIcon={vmwareIcon()}
|
||||
emptyTitle="No vSphere hosts"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Resource } from '@/types/resource';
|
||||
|
||||
const collectSearchCandidates = (resource: Resource): string[] => {
|
||||
const raw: Array<string | undefined> = [
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user