💄 show attached resources in policy list

This commit is contained in:
Fred KISSIE
2026-03-06 04:36:12 +01:00
parent dfe42e9016
commit 37ceba6b81
4 changed files with 91 additions and 5 deletions

View File

@@ -167,6 +167,9 @@
"resourceAdd": "Add Resource", "resourceAdd": "Add Resource",
"resourceErrorDelte": "Error deleting resource", "resourceErrorDelte": "Error deleting resource",
"resourcePoliciesTitle": "Manage Resource Policies", "resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources", "resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...", "resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy", "resourcePoliciesAdd": "Add Policy",
@@ -1069,7 +1072,6 @@
"pageNotFoundDescription": "Oops! The page you're looking for doesn't exist.", "pageNotFoundDescription": "Oops! The page you're looking for doesn't exist.",
"overview": "Overview", "overview": "Overview",
"home": "Home", "home": "Home",
"accessControl": "Access Control",
"settings": "Settings", "settings": "Settings",
"usersAll": "All Users", "usersAll": "All Users",
"license": "License", "license": "License",

View File

@@ -12,11 +12,16 @@ export type GetMaintenanceInfoResponse = {
maintenanceEstimatedTime: string | null; maintenanceEstimatedTime: string | null;
}; };
export type AttachedResource = Pick<
Resource,
"resourceId" | "name" | "fullDomain"
>;
export type ResourcePolicyWithResources = Pick< export type ResourcePolicyWithResources = Pick<
ResourcePolicy, ResourcePolicy,
"resourcePolicyId" | "niceId" | "name" | "orgId" "resourcePolicyId" | "niceId" | "name" | "orgId"
> & { > & {
resources: Array<Pick<Resource, "resourceId" | "name" | "fullDomain">>; resources: Array<AttachedResource>;
}; };
export type ListResourcePoliciesResponse = PaginatedResponse<{ export type ListResourcePoliciesResponse = PaginatedResponse<{

View File

@@ -3,7 +3,6 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { internal } from "@app/lib/api"; import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies"; import { authCookieHeader } from "@app/lib/api/cookies";
import { getCachedOrg } from "@app/lib/api/getCachedOrg"; import { getCachedOrg } from "@app/lib/api/getCachedOrg";
import OrgProvider from "@app/providers/OrgProvider";
import type { GetOrgResponse } from "@server/routers/org"; import type { GetOrgResponse } from "@server/routers/org";
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types"; import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";

View File

@@ -3,9 +3,17 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import { useNavigationContext } from "@app/hooks/useNavigationContext"; import { useNavigationContext } from "@app/hooks/useNavigationContext";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types"; import type {
AttachedResource,
ListResourcePoliciesResponse
} from "@server/routers/resource/types";
import type { PaginationState } from "@tanstack/react-table"; import type { PaginationState } from "@tanstack/react-table";
import { ArrowRight, MoreHorizontal } from "lucide-react"; import {
ArrowRight,
ChevronDown,
MoreHorizontal,
Waypoints
} from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
@@ -20,6 +28,8 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger DropdownMenuTrigger
} from "./ui/dropdown-menu"; } from "./ui/dropdown-menu";
import type { targets } from "@server/db";
import type { TargetHealth } from "./ProxyResourcesTable";
type ResourcePolicyRow = ListResourcePoliciesResponse["policies"][number]; type ResourcePolicyRow = ListResourcePoliciesResponse["policies"][number];
@@ -65,6 +75,63 @@ export function ResourcePoliciesTable({
}); });
}; };
function ResourceListCell({
resources
}: {
resources?: AttachedResource[];
}) {
if (!resources || resources.length === 0) {
return (
<div
id="LOOK_FOR_ME"
className="flex items-center gap-2 text-muted-foreground"
>
<Waypoints className="size-4 flex-none" />
<span className="text-sm">
{t("resourcePoliciesAttachedResourcesEmpty")}
</span>
</div>
);
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="flex items-center gap-2 h-8 px-0 font-normal"
>
<Waypoints className="size-4 flex-none" />
<span className="text-sm">
{t("resourcePoliciesAttachedResources", {
count: resources.length
})}
</span>
<ChevronDown className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="min-w-70">
{resources.map((resource) => (
<DropdownMenuItem
key={resource.resourceId}
className="flex items-center justify-between gap-4"
>
<div className="flex items-center gap-2">
{resource.name}
</div>
<span
className={`capitalize text-muted-foreground`}
>
{resource.fullDomain}
</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
const proxyColumns: ExtendedColumnDef<ResourcePolicyRow>[] = [ const proxyColumns: ExtendedColumnDef<ResourcePolicyRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
@@ -83,6 +150,19 @@ export function ResourcePoliciesTable({
return <span>{row.original.niceId || "-"}</span>; return <span>{row.original.niceId || "-"}</span>;
} }
}, },
{
id: "resources",
accessorKey: "resources",
friendlyName: t("resourcePoliciesAttachedResourcesColumnTitle"),
header: () => (
<span className="p-3">
{t("resourcePoliciesAttachedResourcesColumnTitle")}
</span>
),
cell: ({ row }) => {
return <ResourceListCell resources={row.original.resources} />;
}
},
{ {
id: "actions", id: "actions",
enableHiding: false, enableHiding: false,