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