forked from baron/baron-sso
adminfront: 글로벌 사이드바에 독립적인 '권한 부여' 메뉴 및 전용 대시보드 페이지 추가 완료
This commit is contained in:
@@ -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 />,
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user