🚧 tried to memo proxy resource table, failed

This commit is contained in:
Fred KISSIE
2026-05-11 21:06:20 +02:00
parent 3855486a00
commit d321d7275c

View File

@@ -118,6 +118,11 @@ type ProxyResourcesTableProps = {
initialFilterSite?: Selectedsite | null; initialFilterSite?: Selectedsite | null;
}; };
const booleanSearchFilterSchema = z
.enum(["true", "false"])
.optional()
.catch(undefined);
export default function ProxyResourcesTable({ export default function ProxyResourcesTable({
resources, resources,
orgId, orgId,
@@ -224,389 +229,429 @@ export default function ProxyResourcesTable({
} }
} }
const proxyColumns: ExtendedColumnDef<ResourceRow>[] = [ const clearSiteFilter = () => {
{ handleFilterChange("siteId", undefined);
accessorKey: "name", setSiteFilterOpen(false);
enableHiding: false, };
friendlyName: t("name"),
header: () => {
const nameOrder = getSortDirection("name", searchParams);
const Icon =
nameOrder === "asc"
? ArrowDown01Icon
: nameOrder === "desc"
? ArrowUp10Icon
: ChevronsUpDownIcon;
return ( const onPickSite = (site: Selectedsite) => {
<Button handleFilterChange("siteId", String(site.siteId));
variant="ghost" setSiteFilterOpen(false);
className="p-3" };
onClick={() => toggleSort("name")}
> const siteFilterOpenRef = useRef(siteFilterOpen);
{t("name")} siteFilterOpenRef.current = siteFilterOpen;
<Icon className="ml-2 h-4 w-4" />
</Button> const selectedSiteRef = useRef(selectedSite);
); selectedSiteRef.current = selectedSite;
}
}, const clearSiteFilterRef = useRef(clearSiteFilter);
{ clearSiteFilterRef.current = clearSiteFilter;
id: "niceId",
accessorKey: "nice", const onPickSiteRef = useRef(onPickSite);
friendlyName: t("identifier"), onPickSiteRef.current = onPickSite;
enableHiding: true,
header: () => <span className="p-3">{t("identifier")}</span>, const proxyColumns = useMemo<ExtendedColumnDef<ResourceRow>[]>(() => {
cell: ({ row }) => { const cols: ExtendedColumnDef<ResourceRow>[] = [
return <span>{row.original.nice || "-"}</span>; {
} accessorKey: "name",
}, enableHiding: false,
{ friendlyName: t("name"),
id: "sites", header: () => {
accessorFn: (row) => row.sites.map((s) => s.siteName).join(", "), const nameOrder = getSortDirection("name", searchParams);
friendlyName: t("sites"), const Icon =
header: () => ( nameOrder === "asc"
<Popover open={siteFilterOpen} onOpenChange={setSiteFilterOpen}> ? ArrowDown01Icon
<PopoverTrigger asChild> : nameOrder === "desc"
? ArrowUp10Icon
: ChevronsUpDownIcon;
return (
<Button <Button
type="button"
variant="ghost" variant="ghost"
role="combobox" className="p-3"
className={cn( onClick={() => toggleSort("name")}
"justify-between text-sm h-8 px-2 w-full p-3",
!selectedSite && "text-muted-foreground"
)}
> >
<div className="flex items-center gap-2 min-w-0"> {t("name")}
{t("sites")} <Icon className="ml-2 h-4 w-4" />
<Funnel className="size-4 flex-none" />
{selectedSite && (
<Badge
className="truncate max-w-[10rem]"
variant="secondary"
>
{selectedSite.name}
</Badge>
)}
</div>
</Button> </Button>
</PopoverTrigger> );
<PopoverContent }
className={dataTableFilterPopoverContentClassName} },
align="start" {
id: "niceId",
accessorKey: "nice",
friendlyName: t("identifier"),
enableHiding: true,
header: () => <span className="p-3">{t("identifier")}</span>,
cell: ({ row }) => {
return <span>{row.original.nice || "-"}</span>;
}
},
{
id: "sites",
accessorFn: (row) =>
row.sites.map((s) => s.siteName).join(", "),
friendlyName: t("sites"),
header: () => (
<Popover
open={siteFilterOpenRef.current}
onOpenChange={setSiteFilterOpen}
> >
<div className="border-b p-1"> <PopoverTrigger asChild>
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
size="sm" role="combobox"
className="h-8 w-full justify-start font-normal" className={cn(
onClick={clearSiteFilter} "justify-between text-sm h-8 px-2 w-full p-3",
!selectedSiteRef.current &&
"text-muted-foreground"
)}
> >
{t("standaloneHcFilterAnySite")} <div className="flex items-center gap-2 min-w-0">
{t("sites")}
<Funnel className="size-4 flex-none" />
{selectedSiteRef.current && (
<Badge
className="truncate max-w-[10rem]"
variant="secondary"
>
{selectedSiteRef.current.name}
</Badge>
)}
</div>
</Button> </Button>
</div> </PopoverTrigger>
<SitesSelector <PopoverContent
orgId={orgId} className={dataTableFilterPopoverContentClassName}
selectedSite={selectedSite} align="start"
onSelectSite={onPickSite} >
/> <div className="border-b p-1">
</PopoverContent> <Button
</Popover> type="button"
), variant="ghost"
cell: ({ row }) => ( size="sm"
<ResourceSitesStatusCell className="h-8 w-full justify-start font-normal"
orgId={row.original.orgId} onClick={() => clearSiteFilterRef.current()}
resourceSites={row.original.sites} >
/> {t("standaloneHcFilterAnySite")}
) </Button>
}, </div>
{ <SitesSelector
accessorKey: "protocol", orgId={orgId}
friendlyName: t("protocol"), selectedSite={selectedSiteRef.current}
enableHiding: true, onSelectSite={(site) =>
header: () => <span className="p-3">{t("protocol")}</span>, onPickSiteRef.current(site)
cell: ({ row }) => { }
const resourceRow = row.original; />
return ( </PopoverContent>
<span> </Popover>
{resourceRow.http ),
? resourceRow.ssl cell: ({ row }) => (
? "HTTPS" <ResourceSitesStatusCell
: "HTTP" orgId={row.original.orgId}
: resourceRow.protocol.toUpperCase()} resourceSites={row.original.sites}
</span>
);
}
},
{
id: "status",
accessorKey: "status",
friendlyName: t("health"),
header: () => (
<ColumnFilterButton
options={[
{ value: "healthy", label: t("resourcesTableHealthy") },
{
value: "degraded",
label: t("resourcesTableDegraded")
},
{
value: "unhealthy",
label: t("resourcesTableUnhealthy")
},
{ value: "unknown", label: t("resourcesTableUnknown") }
]}
selectedValue={
searchParams.get("healthStatus") ?? undefined
}
onValueChange={(value) =>
handleFilterChange("healthStatus", value)
}
searchPlaceholder={t("searchPlaceholder")}
emptyMessage={t("emptySearchOptions")}
label={t("health")}
className="p-3"
/>
),
cell: ({ row }) => {
const resourceRow = row.original;
return (
<TargetStatusCell
targets={resourceRow.targets}
healthStatus={resourceRow.health}
/> />
); )
}, },
sortingFn: (rowA, rowB) => { {
const statusA = rowA.original.health; accessorKey: "protocol",
const statusB = rowB.original.health; friendlyName: t("protocol"),
if (!statusA && !statusB) return 0; enableHiding: true,
if (!statusA) return 1; header: () => <span className="p-3">{t("protocol")}</span>,
if (!statusB) return -1; cell: ({ row }) => {
const statusOrder = { const resourceRow = row.original;
healthy: 3,
degraded: 2,
unhealthy: 1,
unknown: 0
};
return statusOrder[statusA] - statusOrder[statusB];
}
},
{
id: "statusHistory",
friendlyName: t("uptime30d"),
header: () => <span className="p-3">{t("uptime30d")}</span>,
cell: ({ row }) => {
const resourceRow = row.original;
return <UptimeMiniBar resourceId={resourceRow.id} days={30} />;
}
},
{
accessorKey: "domain",
friendlyName: t("access"),
header: () => <span className="p-3">{t("access")}</span>,
cell: ({ row }) => {
const resourceRow = row.original;
if (!resourceRow.http) {
return ( return (
<div className="flex items-center gap-2 min-w-0"> <span>
<CopyToClipboard {resourceRow.http
text={resourceRow.proxyPort?.toString() || ""} ? resourceRow.ssl
isLink={false} ? "HTTPS"
/> : "HTTP"
</div> : resourceRow.protocol.toUpperCase()}
</span>
); );
} }
},
if (!resourceRow.domainId) { {
id: "status",
accessorKey: "status",
friendlyName: t("health"),
header: () => (
<ColumnFilterButton
options={[
{
value: "healthy",
label: t("resourcesTableHealthy")
},
{
value: "degraded",
label: t("resourcesTableDegraded")
},
{
value: "unhealthy",
label: t("resourcesTableUnhealthy")
},
{
value: "unknown",
label: t("resourcesTableUnknown")
}
]}
selectedValue={
searchParams.get("healthStatus") ?? undefined
}
onValueChange={(value) =>
handleFilterChange("healthStatus", value)
}
searchPlaceholder={t("searchPlaceholder")}
emptyMessage={t("emptySearchOptions")}
label={t("health")}
className="p-3"
/>
),
cell: ({ row }) => {
const resourceRow = row.original;
return ( return (
<div className="flex items-center gap-2 min-w-0"> <TargetStatusCell
<InfoPopup targets={resourceRow.targets}
info={t("domainNotFoundDescription")} healthStatus={resourceRow.health}
text={t("domainNotFound")} />
/> );
</div> },
sortingFn: (rowA, rowB) => {
const statusA = rowA.original.health;
const statusB = rowB.original.health;
if (!statusA && !statusB) return 0;
if (!statusA) return 1;
if (!statusB) return -1;
const statusOrder = {
healthy: 3,
degraded: 2,
unhealthy: 1,
unknown: 0
};
return statusOrder[statusA] - statusOrder[statusB];
}
},
{
id: "statusHistory",
friendlyName: t("uptime30d"),
header: () => <span className="p-3">{t("uptime30d")}</span>,
cell: ({ row }) => {
const resourceRow = row.original;
return (
<UptimeMiniBar resourceId={resourceRow.id} days={30} />
); );
} }
},
{
accessorKey: "domain",
friendlyName: t("access"),
header: () => <span className="p-3">{t("access")}</span>,
cell: ({ row }) => {
const resourceRow = row.original;
const domainId = resourceRow.domainId; if (!resourceRow.http) {
const certHostname = resourceRow.fullDomain; return (
const showHttpsCertIndicator = <div className="flex items-center gap-2 min-w-0">
build !== "oss" &&
resourceRow.ssl &&
certHostname != null &&
certHostname !== "";
return (
<div className="flex items-center gap-2 min-w-0">
{showHttpsCertIndicator ? (
<ResourceAccessCertIndicator
orgId={resourceRow.orgId}
domainId={domainId}
fullDomain={certHostname}
/>
) : null}
<div className="">
{!resourceRow.wildcard ? (
<CopyToClipboard <CopyToClipboard
text={resourceRow.domain} text={
isLink={true} resourceRow.proxyPort?.toString() || ""
}
isLink={false}
/> />
</div>
);
}
if (!resourceRow.domainId) {
return (
<div className="flex items-center gap-2 min-w-0">
<InfoPopup
info={t("domainNotFoundDescription")}
text={t("domainNotFound")}
/>
</div>
);
}
const domainId = resourceRow.domainId;
const certHostname = resourceRow.fullDomain;
const showHttpsCertIndicator =
build !== "oss" &&
resourceRow.ssl &&
certHostname != null &&
certHostname !== "";
return (
<div className="flex items-center gap-2 min-w-0">
{showHttpsCertIndicator ? (
<ResourceAccessCertIndicator
orgId={resourceRow.orgId}
domainId={domainId}
fullDomain={certHostname}
/>
) : null}
<div className="">
{!resourceRow.wildcard ? (
<CopyToClipboard
text={resourceRow.domain}
isLink={true}
/>
) : (
<span>{resourceRow.domain}</span>
)}
</div>
</div>
);
}
},
{
accessorKey: "authState",
friendlyName: t("authentication"),
header: () => (
<ColumnFilterButton
options={[
{ value: "protected", label: t("protected") },
{
value: "not_protected",
label: t("notProtected")
},
{ value: "none", label: t("none") }
]}
selectedValue={
searchParams.get("authState") ?? undefined
}
onValueChange={(value) =>
handleFilterChange("authState", value)
}
searchPlaceholder={t("searchPlaceholder")}
emptyMessage={t("emptySearchOptions")}
label={t("authentication")}
className="p-3"
/>
),
cell: ({ row }) => {
const resourceRow = row.original;
return (
<div>
{resourceRow.authState === "protected" ? (
<span className="flex items-center space-x-2">
<ShieldCheck className="w-4 h-4 text-green-500" />
<span>{t("protected")}</span>
</span>
) : resourceRow.authState === "not_protected" ? (
<span className="flex items-center space-x-2">
<ShieldOff className="w-4 h-4 text-yellow-500" />
<span>{t("notProtected")}</span>
</span>
) : ( ) : (
<span>{resourceRow.domain}</span> <span>-</span>
)} )}
</div> </div>
</div> );
); }
} },
}, {
{ accessorKey: "enabled",
accessorKey: "authState", friendlyName: t("enabled"),
friendlyName: t("authentication"), header: () => (
header: () => ( <ColumnFilterButton
<ColumnFilterButton options={[
options={[ { value: "true", label: t("enabled") },
{ value: "protected", label: t("protected") }, { value: "false", label: t("disabled") }
{ value: "not_protected", label: t("notProtected") }, ]}
{ value: "none", label: t("none") } selectedValue={booleanSearchFilterSchema.parse(
]} searchParams.get("enabled")
selectedValue={searchParams.get("authState") ?? undefined}
onValueChange={(value) =>
handleFilterChange("authState", value)
}
searchPlaceholder={t("searchPlaceholder")}
emptyMessage={t("emptySearchOptions")}
label={t("authentication")}
className="p-3"
/>
),
cell: ({ row }) => {
const resourceRow = row.original;
return (
<div>
{resourceRow.authState === "protected" ? (
<span className="flex items-center space-x-2">
<ShieldCheck className="w-4 h-4 text-green-500" />
<span>{t("protected")}</span>
</span>
) : resourceRow.authState === "not_protected" ? (
<span className="flex items-center space-x-2">
<ShieldOff className="w-4 h-4 text-yellow-500" />
<span>{t("notProtected")}</span>
</span>
) : (
<span>-</span>
)} )}
</div> onValueChange={(value) =>
); handleFilterChange("enabled", value)
} }
}, searchPlaceholder={t("searchPlaceholder")}
{ emptyMessage={t("emptySearchOptions")}
accessorKey: "enabled", label={t("enabled")}
friendlyName: t("enabled"), className="p-3"
header: () => ( />
<ColumnFilterButton ),
options={[ cell: ({ row }) => (
{ value: "true", label: t("enabled") }, <ResourceEnabledForm
{ value: "false", label: t("disabled") } resource={row.original}
]} onToggleResourceEnabled={toggleResourceEnabled}
selectedValue={booleanSearchFilterSchema.parse( />
searchParams.get("enabled") )
)} },
onValueChange={(value) => {
handleFilterChange("enabled", value) id: "actions",
} enableHiding: false,
searchPlaceholder={t("searchPlaceholder")} header: () => <span className="p-3"></span>,
emptyMessage={t("emptySearchOptions")} cell: ({ row }) => {
label={t("enabled")} const resourceRow = row.original;
className="p-3" return (
/> <div className="flex items-center gap-2 justify-end">
), <DropdownMenu>
cell: ({ row }) => ( <DropdownMenuTrigger asChild>
<ResourceEnabledForm <Button
resource={row.original} variant="ghost"
onToggleResourceEnabled={toggleResourceEnabled} className="h-8 w-8 p-0"
/> >
) <span className="sr-only">
}, {t("openMenu")}
...(isLabelFeatureEnabled </span>
? [ <MoreHorizontal className="h-4 w-4" />
{ </Button>
id: "labels", </DropdownMenuTrigger>
accessorKey: "labels", <DropdownMenuContent align="end">
header: () => ( <Link
<span className="p-3 text-end w-full inline-block"> className="block w-full"
{t("labels")} href={`/${resourceRow.orgId}/settings/resources/proxy/${resourceRow.nice}`}
</span> >
), <DropdownMenuItem>
cell: ({ row }: { row: { original: ResourceRow } }) => { {t("viewSettings")}
return ( </DropdownMenuItem>
<ResourceLabelCell </Link>
resource={row.original} <DropdownMenuItem
orgId={orgId} onClick={() => {
/> setSelectedResource(resourceRow);
); setIsDeleteModalOpen(true);
} }}
} >
] <span className="text-red-500">
: []), {t("delete")}
{ </span>
id: "actions",
enableHiding: false,
header: () => <span className="p-3"></span>,
cell: ({ row }) => {
const resourceRow = row.original;
return (
<div className="flex items-center gap-2 justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">
{t("openMenu")}
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<Link
className="block w-full"
href={`/${resourceRow.orgId}/settings/resources/proxy/${resourceRow.nice}`}
>
<DropdownMenuItem>
{t("viewSettings")}
</DropdownMenuItem> </DropdownMenuItem>
</Link> </DropdownMenuContent>
<DropdownMenuItem </DropdownMenu>
onClick={() => { <Link
setSelectedResource(resourceRow); href={`/${resourceRow.orgId}/settings/resources/proxy/${resourceRow.nice}`}
setIsDeleteModalOpen(true); >
}} <Button variant={"outline"}>
> {t("edit")}
<span className="text-red-500"> <ArrowRight className="ml-2 w-4 h-4" />
{t("delete")} </Button>
</span> </Link>
</DropdownMenuItem> </div>
</DropdownMenuContent> );
</DropdownMenu> }
<Link
href={`/${resourceRow.orgId}/settings/resources/proxy/${resourceRow.nice}`}
>
<Button variant={"outline"}>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div>
);
} }
} ];
];
const booleanSearchFilterSchema = z if (isLabelFeatureEnabled) {
.enum(["true", "false"]) cols.splice(cols.length - 1, 0, {
.optional() id: "labels",
.catch(undefined); accessorKey: "labels",
header: () => (
<span className="p-3 text-end w-full inline-block">
{t("labels")}
</span>
),
cell: ({ row }: { row: { original: ResourceRow } }) => (
<ResourceLabelCell resource={row.original} orgId={orgId} />
)
});
}
return cols;
}, [isLabelFeatureEnabled, orgId, t, searchParams]);
function handleFilterChange( function handleFilterChange(
column: string, column: string,
@@ -623,16 +668,6 @@ export default function ProxyResourcesTable({
}); });
} }
const clearSiteFilter = () => {
handleFilterChange("siteId", undefined);
setSiteFilterOpen(false);
};
const onPickSite = (site: Selectedsite) => {
handleFilterChange("siteId", String(site.siteId));
setSiteFilterOpen(false);
};
function toggleSort(column: string) { function toggleSort(column: string) {
const newSearch = getNextSortOrder(column, searchParams); const newSearch = getNextSortOrder(column, searchParams);