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",