diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_components/rbac-dialog-content.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_components/rbac-dialog-content.tsx new file mode 100644 index 0000000000..e377c7b33e --- /dev/null +++ b/apps/dashboard/app/(app)/apis/[apiId]/_components/rbac-dialog-content.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { Badge } from "@/components/ui/badge"; +import { formatNumber } from "@/lib/fmt"; +import { trpc } from "@/lib/trpc/client"; +import { AnimatedLoadingSpinner, Button } from "@unkey/ui"; +import dynamic from "next/dynamic"; + +const PermissionList = dynamic( + () => + import("../keys/[keyAuthId]/[keyId]/components/rbac/permissions").then((mod) => ({ + default: mod.PermissionList, + })), + { ssr: false }, +); + +const RBACButtons = dynamic( + () => + import("../keys/[keyAuthId]/[keyId]/components/rbac/rbac-buttons").then((mod) => ({ + default: mod.RBACButtons, + })), + { ssr: false }, +); + +type Props = { + keyId: string; + keyspaceId: string; +}; + +export default function RBACDialogContent({ keyId, keyspaceId }: Props) { + const trpcUtils = trpc.useUtils(); + + const { + data: permissionsData, + isLoading, + isRefetching, + error, + } = trpc.key.fetchPermissions.useQuery({ + keyId, + keyspaceId, + }); + + const { transientPermissionIds, rolesList } = calculatePermissionData(permissionsData); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error || !permissionsData) { + return ( +
+
Could not retrieve permission data
+
+ There was an error loading the permissions for this key. Please try again or contact + support if the issue persists. +
+ +
+ ); + } + + return ( +
+
+
+ + {formatNumber(permissionsData.roles.length)} Roles{" "} + + + {formatNumber(transientPermissionIds.size)} Permissions + +
+ +
+
+ {keyId ? ( + + ) : ( +
+
No key selected
+
+ )} +
+
+ ); +} + +type WorkspaceRole = { + id: string; + name: string; + permissions: { permissionId: string }[]; +}; + +type PermissionsResponse = { + roles: { roleId: string }[]; + workspace: { roles: WorkspaceRole[]; permissions: { roles: unknown } }; +}; + +function calculatePermissionData(permissionsData?: PermissionsResponse) { + const transientPermissionIds = new Set(); + const rolesList: { id: string; name: string; isActive: boolean }[] = []; + + if (!permissionsData) { + return { transientPermissionIds, rolesList }; + } + + // Mimic the original implementation logic + const connectedRoleIds = new Set(); + + for (const role of permissionsData.roles) { + connectedRoleIds.add(role.roleId); + } + + for (const role of permissionsData.workspace.roles) { + if (connectedRoleIds.has(role.id)) { + for (const p of role.permissions) { + transientPermissionIds.add(p.permissionId); + } + } + } + + // Build roles list matching the original format + const roles = permissionsData.workspace.roles.map((role: any) => { + return { + id: role.id, + name: role.name, + isActive: permissionsData.roles.some((keyRole: any) => keyRole.roleId === role.id), + }; + }); + + return { + transientPermissionIds, + rolesList: roles, + }; +} diff --git a/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx b/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx index eb943aa2e4..524982cc17 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx @@ -4,17 +4,11 @@ import { QuickNavPopover } from "@/components/navbar-popover"; import { NavbarActionButton } from "@/components/navigation/action-button"; import { CopyableIDButton } from "@/components/navigation/copyable-id-button"; import { Navbar } from "@/components/navigation/navbar"; -import { Badge } from "@/components/ui/badge"; import { useIsMobile } from "@/hooks/use-mobile"; -import { formatNumber } from "@/lib/fmt"; -import { trpc } from "@/lib/trpc/client"; import type { KeyDetails } from "@/lib/trpc/routers/api/keys/query-api-keys/schema"; import { ChevronExpandY, Gauge, Gear, Plus, ShieldKey } from "@unkey/icons"; -import { AnimatedLoadingSpinner, Button } from "@unkey/ui"; import dynamic from "next/dynamic"; import { useState } from "react"; -import { PermissionList } from "./keys/[keyAuthId]/[keyId]/components/rbac/permissions"; -import { RBACButtons } from "./keys/[keyAuthId]/[keyId]/components/rbac/rbac-buttons"; import { getKeysTableActionItems } from "./keys/[keyAuthId]/_components/components/table/components/actions/keys-table-action.popover.constants"; const CreateKeyDialog = dynamic( @@ -56,6 +50,16 @@ const KeysTableActionPopover = dynamic( }, ); +const RBACDialogContent = dynamic(() => import("./_components/rbac-dialog-content"), { + ssr: false, + loading: () => ( + + + Permissions + + ), +}); + export const ApisNavbar = ({ api, apis, @@ -82,29 +86,10 @@ export const ApisNavbar = ({ keyData?: KeyDetails | null; }) => { const isMobile = useIsMobile(); - const trpcUtils = trpc.useUtils(); const [showRBAC, setShowRBAC] = useState(false); const keyId = keyData?.id || ""; const keyspaceId = api.keyAuthId || ""; - const shouldFetchPermissions = Boolean(keyId) && Boolean(keyspaceId); - - const { - data: permissionsData, - isLoading, - isRefetching, - error, - } = trpc.key.fetchPermissions.useQuery( - { - keyId, - keyspaceId, - }, - { - enabled: shouldFetchPermissions, - }, - ); - - const { transientPermissionIds, rolesList } = calculatePermissionData(permissionsData); return ( <> @@ -181,7 +166,7 @@ export const ApisNavbar = ({ setShowRBAC(true)} - disabled={!shouldFetchPermissions} + disabled={!(keyId && keyspaceId)} > Permissions @@ -208,117 +193,17 @@ export const ApisNavbar = ({ )} - setShowRBAC(false)} - title="Key Permissions & Roles" - subTitle="Manage access control for this API key with role-based permissions" - className="max-w-[800px] max-h-[90vh] overflow-y-auto" - > - {isLoading ? ( -
- -
- ) : error || !permissionsData ? ( -
-
Could not retrieve permission data
-
- There was an error loading the permissions for this key. Please try again or contact - support if the issue persists. -
- -
- ) : ( -
-
-
- - {formatNumber(permissionsData.roles.length)} Roles{" "} - - - {formatNumber(transientPermissionIds.size)} Permissions - -
- -
-
- {/* Only render PermissionList if we have a valid keyId */} - {keyId ? ( - - ) : ( -
-
No key selected
-
- )} -
-
- )} -
+ {showRBAC && ( + setShowRBAC(false)} + title="Key Permissions & Roles" + subTitle="Manage access control for this API key with role-based permissions" + className="max-w-[800px] max-h-[90vh] overflow-y-auto" + > + + + )} ); }; - -type WorkspaceRole = { - id: string; - name: string; - permissions: { permissionId: string }[]; -}; - -type PermissionsResponse = { - roles: { roleId: string }[]; - workspace: { roles: WorkspaceRole[]; permissions: { roles: unknown } }; -}; - -function calculatePermissionData(permissionsData?: PermissionsResponse) { - const transientPermissionIds = new Set(); - const rolesList: { id: string; name: string; isActive: boolean }[] = []; - - if (!permissionsData) { - return { transientPermissionIds, rolesList }; - } - - // Mimic the original implementation logic - const connectedRoleIds = new Set(); - - for (const role of permissionsData.roles) { - connectedRoleIds.add(role.roleId); - } - - for (const role of permissionsData.workspace.roles) { - if (connectedRoleIds.has(role.id)) { - for (const p of role.permissions) { - transientPermissionIds.add(p.permissionId); - } - } - } - - // Build roles list matching the original format - const roles = permissionsData.workspace.roles.map((role: any) => { - return { - id: role.id, - name: role.name, - isActive: permissionsData.roles.some((keyRole: any) => keyRole.roleId === role.id), - }; - }); - - return { - transientPermissionIds, - rolesList: roles, - }; -} diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx index 939399687f..28e03ebe78 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx @@ -2,7 +2,7 @@ import { VirtualTable } from "@/components/virtual-table/index"; import type { Column } from "@/components/virtual-table/types"; import type { KeyDetails } from "@/lib/trpc/routers/api/keys/query-api-keys/schema"; -import { BookBookmark, Focus, Key } from "@unkey/icons"; +import { BookBookmark, Dots, Focus, Key } from "@unkey/icons"; import { AnimatedLoadingSpinner, Button, @@ -14,9 +14,9 @@ import { TooltipTrigger, } from "@unkey/ui"; import { cn } from "@unkey/ui/src/lib/utils"; +import dynamic from "next/dynamic"; import Link from "next/link"; import React, { useCallback, useMemo, useState } from "react"; -import { KeysTableActionPopover } from "./components/actions/keys-table-action.popover"; import { getKeysTableActionItems } from "./components/actions/keys-table-action.popover.constants"; import { VerificationBarChart } from "./components/bar-chart"; import { HiddenValueCell } from "./components/hidden-value"; @@ -34,6 +34,27 @@ import { StatusDisplay } from "./components/status-cell"; import { useKeysListQuery } from "./hooks/use-keys-list-query"; import { getRowClassName } from "./utils/get-row-class"; +const KeysTableActionPopover = dynamic( + () => + import("./components/actions/keys-table-action.popover").then((mod) => ({ + default: mod.KeysTableActionPopover, + })), + { + ssr: false, + loading: () => ( + + ), + }, +); + export const KeysList = ({ keyspaceId, apiId,