diff --git a/adminfront/src/app/routes.tsx b/adminfront/src/app/routes.tsx index 472d42e4..5603a013 100644 --- a/adminfront/src/app/routes.tsx +++ b/adminfront/src/app/routes.tsx @@ -18,6 +18,7 @@ import TenantListPage from "../features/tenants/routes/TenantListPage"; import { TenantProfilePage } from "../features/tenants/routes/TenantProfilePage"; import { TenantSchemaPage } from "../features/tenants/routes/TenantSchemaPage"; import { TenantFineGrainedPermissionsTab } from "../features/tenants/routes/TenantFineGrainedPermissionsTab"; +import { TenantFineGrainedPermissionsPage } from "../features/tenants/routes/TenantFineGrainedPermissionsPage"; import { TenantWorksmobilePage } from "../features/tenants/routes/TenantWorksmobilePage"; import TenantUserGroupsTab from "../features/user-groups/routes/TenantUserGroupsTab"; import GlobalCustomClaimsPage from "../features/users/GlobalCustomClaimsPage"; @@ -52,6 +53,7 @@ export const adminRoutes: RouteObject[] = [ { path: "tenants", element: }, { path: "tenants/new", element: }, { path: "worksmobile", element: }, + { path: "permissions-direct", element: }, { path: "tenants/:tenantId", element: , diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx index 3cf9fcfd..d378f9e1 100644 --- a/adminfront/src/components/layout/AppLayout.tsx +++ b/adminfront/src/components/layout/AppLayout.tsx @@ -62,6 +62,12 @@ const staticNavItems: ShellSidebarNavItem[] = [ to: "/users", icon: Users, }, + { + labelKey: "ui.admin.nav.permissions_direct", + labelFallback: "Direct Permissions", + to: "/permissions-direct", + icon: ShieldCheck, + }, { labelKey: "ui.admin.nav.auth_guard", labelFallback: "Auth Guard", @@ -208,6 +214,7 @@ function AppLayout() { }); const filteredItems = items.filter((item) => { if (item.to === "/api-keys") return isSuperAdmin; + if (item.to === "/permissions-direct") return isSuperAdmin || _manageableCount > 0; return true; }); diff --git a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx new file mode 100644 index 00000000..cc7cfaca --- /dev/null +++ b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx @@ -0,0 +1,87 @@ +import { useQuery } from "@tanstack/react-query"; +import { useState } from "react"; +import { fetchAllTenants, fetchMe } from "../../../lib/adminApi"; +import { t } from "../../../lib/i18n"; +import { TenantFineGrainedPermissionsTab } from "./TenantFineGrainedPermissionsTab"; +import { ShieldCheck } from "lucide-react"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "../../../components/ui/card"; + +export function TenantFineGrainedPermissionsPage() { + const [selectedTenantId, setSelectedTenantId] = useState(""); + + const { data: profile } = useQuery({ + queryKey: ["me"], + queryFn: fetchMe, + }); + + const isSuperAdmin = profile?.role === "super_admin"; + + const tenantsQuery = useQuery({ + queryKey: ["tenants", "list-all"], + queryFn: () => fetchAllTenants(), + enabled: isSuperAdmin, + }); + + const tenants = isSuperAdmin + ? (tenantsQuery.data?.items ?? []) + : (profile?.manageableTenants ?? []); + + return ( +
+
+

+ + {t("ui.admin.nav.permissions_direct", "권한 부여")} +

+

+ {t( + "msg.admin.permissions_direct.description", + "선택한 테넌트의 각 기능별 세부 조회 및 수정 권한을 지정하고 부여합니다.", + )} +

+
+ + + + + {t("ui.admin.permissions_direct.select_tenant", "대상 테넌트 선택")} + + + {t( + "msg.admin.permissions_direct.select_tenant_desc", + "권한을 부여할 대상 테넌트를 리스트에서 선택해 주세요.", + )} + + + + + + + + {selectedTenantId ? ( + + ) : ( +
+ {t("msg.admin.permissions_direct.select_prompt", "상단에서 테넌트를 선택하면 세부 권한 격리 설정 격자가 노출됩니다.")} +
+ )} +
+ ); +} diff --git a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx index d2020ce5..9f801e8a 100644 --- a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx +++ b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx @@ -45,9 +45,13 @@ import { import { t } from "../../../lib/i18n"; import { Trash2 } from "lucide-react"; -export function TenantFineGrainedPermissionsTab() { +interface TenantFineGrainedPermissionsTabProps { + tenantIdProp?: string; +} + +export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrainedPermissionsTabProps = {}) { const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>(); - const tenantId = tenantIdParam ?? ""; + const tenantId = tenantIdProp || tenantIdParam || ""; const { hasPermission } = useTenantPermission(tenantId); const isWritable = hasPermission("manage_admins"); const queryClient = useQueryClient();