diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx index f079901c73..d17c24347f 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { type Interval, IntervalSelect } from "@/app/(app)/apis/[apiId]/select"; import { CreateNewPermission } from "@/app/(app)/authorization/permissions/create-new-permission"; +import type { NestedPermissions } from "@/app/(app)/authorization/roles/[roleId]/tree"; import { CreateNewRole } from "@/app/(app)/authorization/roles/create-new-role"; import { StackedColumnChart } from "@/components/dashboard/charts"; import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder"; @@ -23,7 +24,7 @@ import { cn } from "@/lib/utils"; import { BarChart, Minus } from "lucide-react"; import ms from "ms"; import { notFound } from "next/navigation"; -import { Chart } from "./chart"; +import PermissionTree from "./permission-list"; import { VerificationTable } from "./verification-table"; export default async function APIKeyDetailPage(props: { @@ -71,6 +72,7 @@ export default async function APIKeyDetailPage(props: { }, }, }); + if (!key || key.workspace.tenantId !== tenantId) { return notFound(); } @@ -155,6 +157,38 @@ export default async function APIKeyDetailPage(props: { } } + const roleTee = key.workspace.roles.map((role) => { + const nested: NestedPermissions = {}; + for (const permission of key.workspace.permissions) { + let n = nested; + const parts = permission.name.split("."); + for (let i = 0; i < parts.length; i++) { + const p = parts[i]; + if (!(p in n)) { + n[p] = { + id: permission.id, + name: permission.name, + description: permission.description, + checked: role.permissions.some((p) => p.permissionId === permission.id), + part: p, + permissions: {}, + path: parts.slice(0, i).join("."), + }; + } + n = n[p].permissions; + } + } + const data = { + id: role.id, + name: role.name, + description: role.description, + keyId: key.id, + active: key.roles.some((keyRole) => keyRole.roleId === role.id), + nestedPermissions: nested, + }; + return data; + }); + return (
@@ -308,19 +342,7 @@ export default async function APIKeyDetailPage(props: {
- ({ - ...r, - active: key.roles.some((keyRole) => keyRole.roleId === r.id), - }))} - permissions={key.workspace.permissions.map((p) => ({ - ...p, - active: transientPermissionIds.has(p.id), - }))} - /> + ); diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/permission-list.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/permission-list.tsx new file mode 100644 index 0000000000..a22e3fdac9 --- /dev/null +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/permission-list.tsx @@ -0,0 +1,113 @@ +"use client"; +import { RecursivePermission } from "@/app/(app)/authorization/roles/[roleId]/tree"; +import { CopyButton } from "@/components/dashboard/copy-button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { ChevronRight } from "lucide-react"; +import { useEffect, useState } from "react"; +import { RoleToggle } from "./role-toggle"; + +export type NestedPermission = { + id: string; + checked: boolean; + description: string | null; + name: string; + part: string; + path: string; + permissions: NestedPermissions; +}; + +export type NestedPermissions = Record; + +export type Role = { + id: string; + name: string; + keyId: string; + active: boolean; + description: string | null; + nestedPermissions: NestedPermissions; +}; + +type PermissionTreeProps = { + roles: Role[]; +}; + +export default function PermissionTree({ roles }: PermissionTreeProps) { + const [openAll, setOpenAll] = useState(false); + const [openRoles, setOpenRoles] = useState([]); + + useEffect(() => { + setOpenRoles(openAll ? roles.map((role) => role.id) : []); + }, [openAll, roles]); + + return ( + + +
+ Permissions +
+
+ + +
+
+ + + {roles.map((role) => { + const isOpen = openRoles.includes(role.id); + return ( + { + setOpenRoles((prev) => + open + ? prev.includes(role.id) + ? prev + : [...prev, role.id] + : prev.filter((id) => id !== role.id), + ); + }} + > + + + + + + {role.name} + + +
+ + {role.name} + +
+ +
+
+
+
+
+ +
+ {Object.entries(role.nestedPermissions).map(([k, p]) => ( + + ))} +
+
+
+ ); + })} +
+
+ ); +} diff --git a/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx b/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx index e8af830dc4..8e5746694d 100644 --- a/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx +++ b/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx @@ -56,7 +56,7 @@ export const Tree: React.FC = ({ nestedPermissions, role }) => { ); }; -const RecursivePermission: React.FC< +export const RecursivePermission: React.FC< NestedPermission & { k: string; roleId: string; openAll: boolean } > = ({ k, openAll, id, name, permissions, roleId, checked, description }) => { const [open, setOpen] = useState(openAll); diff --git a/apps/planetfall/app/layout.tsx b/apps/planetfall/app/layout.tsx index 3314e4780a..60570a4e29 100644 --- a/apps/planetfall/app/layout.tsx +++ b/apps/planetfall/app/layout.tsx @@ -2,7 +2,9 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; -const inter = Inter({ subsets: ["latin"] }); +const inter = Inter({ + subsets: ["latin"], +}); export const metadata: Metadata = { title: "Create Next App", diff --git a/apps/workflows/app/layout.tsx b/apps/workflows/app/layout.tsx index bf4fc28121..4e0ed72470 100644 --- a/apps/workflows/app/layout.tsx +++ b/apps/workflows/app/layout.tsx @@ -1,7 +1,9 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; -const inter = Inter({ subsets: ["latin"] }); +const inter = Inter({ + subsets: ["latin"], +}); export const metadata: Metadata = { title: "Create Next App",