From 14e1a119d3c313158a19d65981e96fc01cce8565 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Mon, 11 May 2026 18:24:47 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20WIP:=20showing=20labels=20in=20p?= =?UTF-8?q?roxy=20resources=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/resource/listResources.ts | 36 +-- src/components/ProxyResourcesTable.tsx | 320 +++++++++++++---------- 2 files changed, 195 insertions(+), 161 deletions(-) diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 16a82e400..ab2e41204 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -325,24 +325,6 @@ export async function listResources( ) ]; - if (query) { - conditions.push( - or( - like( - sql`LOWER(${resources.name})`, - "%" + query.toLowerCase() + "%" - ), - like( - sql`LOWER(${resources.niceId})`, - "%" + query.toLowerCase() + "%" - ), - like( - sql`LOWER(${resources.fullDomain})`, - "%" + query.toLowerCase() + "%" - ) - ) - ); - } if (typeof enabled !== "undefined") { conditions.push(eq(resources.enabled, enabled)); } @@ -386,6 +368,24 @@ export async function listResources( .where(and(eq(sites.orgId, orgId), eq(sites.siteId, siteId))); conditions.push(inArray(resources.resourceId, resourcesWithSite)); } + if (query) { + conditions.push( + or( + like( + sql`LOWER(${resources.name})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${resources.niceId})`, + "%" + query.toLowerCase() + "%" + ), + like( + sql`LOWER(${resources.fullDomain})`, + "%" + query.toLowerCase() + "%" + ) + ) + ); + } const baseQuery = queryResourcesBase().where(and(...conditions)); diff --git a/src/components/ProxyResourcesTable.tsx b/src/components/ProxyResourcesTable.tsx index 98ddd8eb7..21a770a68 100644 --- a/src/components/ProxyResourcesTable.tsx +++ b/src/components/ProxyResourcesTable.tsx @@ -2,10 +2,12 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import CopyToClipboard from "@app/components/CopyToClipboard"; +import { ResourceAccessCertIndicator } from "@app/components/ResourceAccessCertIndicator"; import { ResourceSitesStatusCell, type ResourceSiteRow } from "@app/components/ResourceSitesStatusCell"; +import { Selectedsite, SitesSelector } from "@app/components/site-selector"; import { Badge } from "@app/components/ui/badge"; import { Button } from "@app/components/ui/button"; import { ExtendedColumnDef } from "@app/components/ui/data-table"; @@ -24,12 +26,14 @@ import { import { Switch } from "@app/components/ui/switch"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useNavigationContext } from "@app/hooks/useNavigationContext"; -import { Selectedsite, SitesSelector } from "@app/components/site-selector"; +import { usePaidStatus } from "@app/hooks/usePaidStatus"; +import { toast } from "@app/hooks/useToast"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; import { cn } from "@app/lib/cn"; import { dataTableFilterPopoverContentClassName } from "@app/lib/dataTableFilterPopover"; import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn"; -import { toast } from "@app/hooks/useToast"; -import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { build } from "@server/build"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { UpdateResourceResponse } from "@server/routers/resource"; import type { PaginationState } from "@tanstack/react-table"; import { AxiosResponse } from "axios"; @@ -64,8 +68,6 @@ import z from "zod"; import { ColumnFilterButton } from "./ColumnFilterButton"; import { ControlledDataTable } from "./ui/controlled-data-table"; import UptimeMiniBar from "./UptimeMiniBar"; -import { ResourceAccessCertIndicator } from "@app/components/ResourceAccessCertIndicator"; -import { build } from "@server/build"; export type TargetHealth = { targetId: number; @@ -97,31 +99,13 @@ export type ResourceRow = { health?: "healthy" | "degraded" | "unhealthy" | "unknown"; sites: ResourceSiteRow[]; wildcard?: boolean; + labels?: Array<{ + labelId: number; + name: string; + color: string; + }>; }; -function StatusIcon({ - status, - className = "" -}: { - status: string | undefined | null; - className?: string; -}) { - const iconClass = `h-4 w-4 ${className}`; - - switch (status) { - case "healthy": - return ; - case "degraded": - return ; - case "unhealthy": - return ; - case "unknown": - return ; - default: - return null; - } -} - type ProxyResourcesTableProps = { resources: ResourceRow[]; orgId: string; @@ -153,6 +137,9 @@ export default function ProxyResourcesTable({ const [selectedResource, setSelectedResource] = useState(); + const { isPaidUser } = usePaidStatus(); + const isLabelFeatureEnabled = isPaidUser(tierMatrix.labels); + const [isRefreshing, startTransition] = useTransition(); const [isNavigatingToAddPage, startNavigation] = useTransition(); const [siteFilterOpen, setSiteFilterOpen] = useState(false); @@ -233,120 +220,6 @@ export default function ProxyResourcesTable({ } } - function TargetStatusCell({ - targets, - healthStatus - }: { - targets?: TargetHealth[]; - healthStatus?: string; - }) { - const overallStatus = healthStatus; - - if (!targets || targets.length === 0) { - return ( -
- - - {t("resourcesTableNoTargets")} - -
- ); - } - - const monitoredTargets = targets.filter( - (t) => t.enabled && t.healthStatus && t.healthStatus !== "unknown" - ); - const unknownTargets = targets.filter( - (t) => !t.enabled || !t.healthStatus || t.healthStatus === "unknown" - ); - - return ( - - - - - - {monitoredTargets.length > 0 && ( - <> - {monitoredTargets.map((target) => ( - -
- - {target.siteName - ? `${target.siteName} (${target.ip}:${target.port})` - : `${target.ip}:${target.port}`} -
- - {target.healthStatus} - -
- ))} - - )} - {unknownTargets.length > 0 && ( - <> - {unknownTargets.map((target) => ( - -
- - {target.siteName - ? `${target.siteName} (${target.ip}:${target.port})` - : `${target.ip}:${target.port}`} -
- - {!target.enabled - ? t("disabled") - : t("resourcesTableNotMonitored")} - -
- ))} - - )} -
-
- ); - } - const proxyColumns: ExtendedColumnDef[] = [ { accessorKey: "name", @@ -653,6 +526,28 @@ export default function ProxyResourcesTable({ /> ) }, + ...(isLabelFeatureEnabled + ? [ + { + id: "labels", + accessorKey: "labels", + header: () => ( + + {t("labels")} + + ), + cell: ({ row }: { row: { original: ResourceRow } }) => { + return ( + // + <> + ); + } + } + ] + : []), { id: "actions", enableHiding: false, @@ -800,7 +695,11 @@ export default function ProxyResourcesTable({ isRefreshing={isRefreshing || isFiltering} isNavigatingToAddPage={isNavigatingToAddPage} enableColumnVisibility - columnVisibility={{ niceId: false, protocol: false }} + columnVisibility={{ + niceId: false, + protocol: false, + labels: false + }} stickyLeftColumn="name" stickyRightColumn="actions" /> @@ -808,6 +707,118 @@ export default function ProxyResourcesTable({ ); } +function TargetStatusCell({ + targets, + healthStatus +}: { + targets?: TargetHealth[]; + healthStatus?: string; +}) { + const overallStatus = healthStatus; + const t = useTranslations(); + + if (!targets || targets.length === 0) { + return ( +
+ + {t("resourcesTableNoTargets")} +
+ ); + } + + const monitoredTargets = targets.filter( + (t) => t.enabled && t.healthStatus && t.healthStatus !== "unknown" + ); + const unknownTargets = targets.filter( + (t) => !t.enabled || !t.healthStatus || t.healthStatus === "unknown" + ); + + return ( + + + + + + {monitoredTargets.length > 0 && ( + <> + {monitoredTargets.map((target) => ( + +
+ + {target.siteName + ? `${target.siteName} (${target.ip}:${target.port})` + : `${target.ip}:${target.port}`} +
+ + {target.healthStatus} + +
+ ))} + + )} + {unknownTargets.length > 0 && ( + <> + {unknownTargets.map((target) => ( + +
+ + {target.siteName + ? `${target.siteName} (${target.ip}:${target.port})` + : `${target.ip}:${target.port}`} +
+ + {!target.enabled + ? t("disabled") + : t("resourcesTableNotMonitored")} + +
+ ))} + + )} +
+
+ ); +} + type ResourceEnabledFormProps = { resource: ResourceRow; onToggleResourceEnabled: ( @@ -847,3 +858,26 @@ function ResourceEnabledForm({ ); } + +function StatusIcon({ + status, + className = "" +}: { + status: string | undefined | null; + className?: string; +}) { + const iconClass = `h-4 w-4 ${className}`; + + switch (status) { + case "healthy": + return ; + case "degraded": + return ; + case "unhealthy": + return ; + case "unknown": + return ; + default: + return null; + } +}