From 91883397e62624c563f4e8e847b0df25313d8194 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 26 May 2026 22:45:41 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20label=20filter=20column?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/resource/listResources.ts | 42 ++++++++++++++++++++++-- src/components/ProxyResourcesTable.tsx | 19 ++++++++--- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index a4e6588c6..d37f04e13 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -123,7 +123,27 @@ const listResourcesSchema = z.object({ type: "integer", description: "When set, only resources that have at least one target on this site are returned" - }) + }), + labels: z + .preprocess((val) => { + if (val === undefined || val === null || val === "") { + return undefined; + } + if (Array.isArray(val)) { + return val; + } + // the array is returned as this + if (typeof val === "string") { + return val.split(","); + } + return undefined; + }, z.array(z.string())) + .optional() + .catch([]) + .openapi({ + type: "array", + description: "Filter by resource labels" + }) }); // grouped by resource with targets[]) @@ -261,7 +281,8 @@ export async function listResources( healthStatus, sort_by, order, - siteId + siteId, + labels: labelFilter } = parsedQuery.data; const parsedParams = listResourcesParamsSchema.safeParse(req.params); @@ -379,6 +400,23 @@ export async function listResources( .where(and(eq(sites.orgId, orgId), eq(sites.siteId, siteId))); conditions.push(inArray(resources.resourceId, resourcesWithSite)); } + + if (isLabelFeatureEnabled && labelFilter && labelFilter.length > 0) { + conditions.push( + inArray( + resources.resourceId, + db + .select({ id: resourceLabels.resourceId }) + .from(resourceLabels) + .innerJoin( + labels, + eq(labels.labelId, resourceLabels.labelId) + ) + .where(inArray(labels.name, labelFilter)) + ) + ); + } + if (query) { const q = "%" + query.toLowerCase() + "%"; const queryList = [ diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index 51c3254b6..0799afd95 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -72,6 +72,7 @@ import { ControlledDataTable } from "./ui/controlled-data-table"; import UptimeMiniBar from "./UptimeMiniBar"; import { LabelsSelector, type SelectedLabel } from "./labels-selector"; import { LabelBadge } from "./label-badge"; +import { LabelColumnFilterButton } from "./LabelColumnFilterButton"; export type TargetHealth = { targetId: number; @@ -640,9 +641,15 @@ export default function ProxyResourcesTable({ id: "labels", accessorKey: "labels", header: () => ( - - {t("labels")} - + + handleFilterChange("labels", value) + } + label={t("labels")} + className="p-3" + /> ), cell: ({ row }: { row: { original: ResourceRow } }) => ( @@ -655,13 +662,15 @@ export default function ProxyResourcesTable({ function handleFilterChange( column: string, - value: string | undefined | null + value: string | undefined | null | string[] ) { searchParams.delete(column); searchParams.delete("page"); - if (value) { + if (typeof value === "string") { searchParams.set(column, value); + } else if (value) { + value.forEach((val) => searchParams.append(column, val)); } filter({ searchParams