filter by auth state

This commit is contained in:
Fred KISSIE
2026-02-04 03:42:05 +01:00
parent bb1a375484
commit 1fc40b3017
2 changed files with 107 additions and 72 deletions

View File

@@ -17,7 +17,18 @@ import {
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { sql, eq, or, inArray, and, count, ilike } from "drizzle-orm"; import {
sql,
eq,
or,
inArray,
and,
count,
ilike,
asc,
not,
isNull
} from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { fromZodError } from "zod-validation-error"; import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
@@ -48,7 +59,7 @@ const listResourcesSchema = z.object({
.optional() .optional()
.catch(undefined), .catch(undefined),
authState: z authState: z
.enum(["protected", "not_protected"]) .enum(["protected", "not_protected", "none"])
.optional() .optional()
.catch(undefined) .catch(undefined)
}); });
@@ -277,9 +288,63 @@ export async function listResources(
conditions = and(conditions, eq(resources.enabled, enabled)); conditions = and(conditions, eq(resources.enabled, enabled));
} }
if (typeof authState !== "undefined") {
switch (authState) {
case "none":
conditions = and(conditions, eq(resources.http, false));
break;
case "protected":
conditions = and(
conditions,
or(
eq(resources.sso, true),
eq(resources.emailWhitelistEnabled, true),
not(isNull(resourceHeaderAuth.headerAuthId)),
not(isNull(resourcePincode.pincodeId)),
not(isNull(resourcePassword.passwordId))
)
);
break;
case "not_protected":
conditions = and(
conditions,
not(eq(resources.sso, true)),
not(eq(resources.emailWhitelistEnabled, true)),
isNull(resourceHeaderAuth.headerAuthId),
isNull(resourcePincode.pincodeId),
isNull(resourcePassword.passwordId)
);
break;
}
}
const countQuery: any = db const countQuery: any = db
.select({ count: count() }) .select({ count: count() })
.from(resources) .from(resources)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(resourceHeaderAuth.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
.leftJoin(
targetHealthCheck,
eq(targetHealthCheck.targetId, targets.targetId)
)
.where(conditions); .where(conditions);
const baseQuery = queryResourcesBase(); const baseQuery = queryResourcesBase();
@@ -287,7 +352,8 @@ export async function listResources(
const rows: JoinedRow[] = await baseQuery const rows: JoinedRow[] = await baseQuery
.where(conditions) .where(conditions)
.limit(pageSize) .limit(pageSize)
.offset(pageSize * (page - 1)); .offset(pageSize * (page - 1))
.orderBy(asc(resources.resourceId));
// avoids TS issues with reduce/never[] // avoids TS issues with reduce/never[]
const map = new Map<number, ResourceWithTargets>(); const map = new Map<number, ResourceWithTargets>();

View File

@@ -185,23 +185,24 @@ export default function ProxyResourcesTable({
}; };
async function toggleResourceEnabled(val: boolean, resourceId: number) { async function toggleResourceEnabled(val: boolean, resourceId: number) {
await api try {
.post<AxiosResponse<UpdateResourceResponse>>( await api.post<AxiosResponse<UpdateResourceResponse>>(
`resource/${resourceId}`, `resource/${resourceId}`,
{ {
enabled: val enabled: val
} }
) );
.catch((e) => { router.refresh();
toast({ } catch (e) {
variant: "destructive", toast({
title: t("resourcesErrorUpdate"), variant: "destructive",
description: formatAxiosError( title: t("resourcesErrorUpdate"),
e, description: formatAxiosError(
t("resourcesErrorUpdateDescription") e,
) t("resourcesErrorUpdateDescription")
}); )
}); });
}
} }
function TargetStatusCell({ targets }: { targets?: TargetHealth[] }) { function TargetStatusCell({ targets }: { targets?: TargetHealth[] }) {
@@ -313,38 +314,14 @@ export default function ProxyResourcesTable({
accessorKey: "name", accessorKey: "name",
enableHiding: false, enableHiding: false,
friendlyName: t("name"), friendlyName: t("name"),
header: ({ column }) => { header: () => <span className="p-3">{t("name")}</span>
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("name")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
}, },
{ {
id: "niceId", id: "niceId",
accessorKey: "nice", accessorKey: "nice",
friendlyName: t("identifier"), friendlyName: t("identifier"),
enableHiding: true, enableHiding: true,
header: ({ column }) => { header: () => <span className="p-3">{t("identifier")}</span>,
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("identifier")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => { cell: ({ row }) => {
return <span>{row.original.nice || "-"}</span>; return <span>{row.original.nice || "-"}</span>;
} }
@@ -370,19 +347,7 @@ export default function ProxyResourcesTable({
id: "status", id: "status",
accessorKey: "status", accessorKey: "status",
friendlyName: t("status"), friendlyName: t("status"),
header: ({ column }) => { header: () => <span className="p-3">{t("status")}</span>,
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("status")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
return <TargetStatusCell targets={resourceRow.targets} />; return <TargetStatusCell targets={resourceRow.targets} />;
@@ -430,19 +395,23 @@ export default function ProxyResourcesTable({
{ {
accessorKey: "authState", accessorKey: "authState",
friendlyName: t("authentication"), friendlyName: t("authentication"),
header: ({ column }) => { header: () => (
return ( <ColumnFilterButton
<Button options={[
variant="ghost" { value: "protected", label: t("protected") },
onClick={() => { value: "not_protected", label: t("notProtected") },
column.toggleSorting(column.getIsSorted() === "asc") { value: "none", label: t("none") }
} ]}
> selectedValue={searchParams.get("authState") ?? undefined}
{t("authentication")} onValueChange={(value) =>
<ArrowUpDown className="ml-2 h-4 w-4" /> handleFilterChange("authState", value)
</Button> }
); searchPlaceholder={t("searchPlaceholder")}
}, emptyMessage={t("emptySearchOptions")}
label={t("authentication")}
className="p-3"
/>
),
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
return ( return (
@@ -487,16 +456,16 @@ export default function ProxyResourcesTable({
), ),
cell: ({ row }) => ( cell: ({ row }) => (
<Switch <Switch
defaultChecked={ checked={
row.original.http row.original.http
? !!row.original.domainId && row.original.enabled ? !!row.original.domainId && row.original.enabled
: row.original.enabled : row.original.enabled
} }
disabled={ disabled={row.original.http && !row.original.domainId}
row.original.http ? !row.original.domainId : false
}
onCheckedChange={(val) => onCheckedChange={(val) =>
toggleResourceEnabled(val, row.original.id) startTransition(() =>
toggleResourceEnabled(val, row.original.id)
)
} }
/> />
) )