1
0
forked from baron/baron-sso

adminfront: 글로벌 사이드바에 독립적인 '권한 부여' 메뉴 및 전용 대시보드 페이지 추가 완료

This commit is contained in:
2026-06-10 15:57:07 +09:00
parent 6ebcb43b16
commit 2fe15efeca
4 changed files with 102 additions and 2 deletions

View File

@@ -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: <TenantListPage /> },
{ path: "tenants/new", element: <TenantCreatePage /> },
{ path: "worksmobile", element: <TenantWorksmobilePage /> },
{ path: "permissions-direct", element: <TenantFineGrainedPermissionsPage /> },
{
path: "tenants/:tenantId",
element: <TenantDetailPage />,

View File

@@ -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;
});

View File

@@ -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 (
<div className="space-y-6">
<div className="flex flex-col gap-1">
<h1 className="text-3xl font-bold tracking-tight flex items-center gap-2">
<ShieldCheck className="h-8 w-8 text-primary" />
{t("ui.admin.nav.permissions_direct", "권한 부여")}
</h1>
<p className="text-muted-foreground">
{t(
"msg.admin.permissions_direct.description",
"선택한 테넌트의 각 기능별 세부 조회 및 수정 권한을 지정하고 부여합니다.",
)}
</p>
</div>
<Card className="border-none shadow-sm bg-[var(--color-panel)]">
<CardHeader className="pb-4">
<CardTitle className="text-lg font-semibold">
{t("ui.admin.permissions_direct.select_tenant", "대상 테넌트 선택")}
</CardTitle>
<CardDescription>
{t(
"msg.admin.permissions_direct.select_tenant_desc",
"권한을 부여할 대상 테넌트를 리스트에서 선택해 주세요.",
)}
</CardDescription>
</CardHeader>
<CardContent>
<select
value={selectedTenantId}
onChange={(e) => setSelectedTenantId(e.target.value)}
className="flex h-10 w-full max-w-[360px] rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="">{t("ui.admin.permissions_direct.placeholder", "-- 테넌트 선택 --")}</option>
{tenants.map((tenant) => (
<option key={tenant.id} value={tenant.id}>
{tenant.name} ({tenant.slug})
</option>
))}
</select>
</CardContent>
</Card>
{selectedTenantId ? (
<TenantFineGrainedPermissionsTab tenantIdProp={selectedTenantId} />
) : (
<div className="rounded-lg border border-dashed border-border p-12 text-center text-muted-foreground">
{t("msg.admin.permissions_direct.select_prompt", "상단에서 테넌트를 선택하면 세부 권한 격리 설정 격자가 노출됩니다.")}
</div>
)}
</div>
);
}

View File

@@ -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();