1
0
forked from baron/baron-sso
Files
baron-sso/adminfront/src/features/overview/GlobalOverviewPage.tsx
2026-02-13 15:15:21 +09:00

227 lines
7.4 KiB
TypeScript

import {
Activity,
ArrowUpRight,
Box,
Database,
ShieldCheck,
Users,
} from "lucide-react";
import { Link } from "react-router-dom";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "../../components/ui/card";
import { t } from "../../lib/i18n";
import PermissionChecker from "./components/PermissionChecker";
const summaryCards = [
{
labelKey: "ui.admin.overview.summary.total_tenants",
labelFallback: "Total Tenants",
value: "-",
hintKey: "msg.admin.overview.summary.total_tenants",
hintFallback: "Tenant-aware core",
icon: Users,
},
{
labelKey: "ui.admin.overview.summary.oidc_clients",
labelFallback: "OIDC Clients",
value: "-",
hintKey: "msg.admin.overview.summary.oidc_clients",
hintFallback: "Hydra registry",
icon: ShieldCheck,
},
{
labelKey: "ui.admin.overview.summary.audit_events_24h",
labelFallback: "Audit Events (24h)",
value: "-",
hintKey: "msg.admin.overview.summary.audit_events_24h",
hintFallback: "ClickHouse stream",
icon: Activity,
},
{
labelKey: "ui.admin.overview.summary.policy_gate",
labelFallback: "Policy Gate",
value: "Planned",
hintKey: "msg.admin.overview.summary.policy_gate",
hintFallback: "Keto + Admin checks",
icon: Database,
},
];
function GlobalOverviewPage() {
return (
<div className="space-y-10">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--color-muted)]">
{t("ui.admin.overview.kicker", "Global Overview")}
</p>
<h2 className="text-3xl font-semibold">
{t("ui.admin.overview.title", "Tenant-independent control plane")}
</h2>
<p className="text-sm text-[var(--color-muted)]">
{t(
"msg.admin.overview.description",
"모든 테넌트 공통 지표와 정책 상태를 한 곳에서 확인합니다.",
)}
</p>
</div>
<div className="flex items-center gap-2">
<Badge variant="muted">
{t("msg.admin.overview.idp_primary", "IDP: Ory primary")}
</Badge>
<Badge variant="muted">
{t("msg.admin.overview.idp_fallback", "Fallback: Descope")}
</Badge>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
{summaryCards.map(
({
labelKey,
labelFallback,
value,
hintKey,
hintFallback,
icon: Icon,
}) => (
<Card key={labelKey} className="bg-[var(--color-panel)]">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardDescription>{t(labelKey, labelFallback)}</CardDescription>
<div className="rounded-full border border-[var(--color-border)] p-2 text-[var(--color-muted)]">
<Icon size={16} />
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-semibold">{value}</div>
<p className="mt-1 text-xs text-[var(--color-muted)]">
{t(hintKey, hintFallback)}
</p>
</CardContent>
</Card>
),
)}
</div>
<div className="grid gap-6 lg:grid-cols-[1.4fr,1fr]">
<Card className="bg-[var(--color-panel)]">
<CardHeader>
<CardTitle className="text-xl">
{t("ui.admin.overview.playbook.title", "Admin playbook")}
</CardTitle>
<CardDescription>
{t(
"msg.admin.overview.playbook.description",
"운영 정책, 레이트리밋, 감사 로그의 기본 룰을 요약합니다.",
)}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-[var(--color-muted)]">
<div className="flex items-start gap-3">
<div className="mt-1 rounded-full border border-[var(--color-border)] p-2">
<ShieldCheck size={14} />
</div>
<div>
<p className="font-semibold text-foreground">
{t(
"msg.admin.overview.playbook.idp_title",
"Backend-only IDP access",
)}
</p>
<p>
{t(
"msg.admin.overview.playbook.idp_body",
"모든 IDP 호출은 backend를 통해서만 수행하며, Hydra/Kratos admin 포트는 외부에 노출하지 않습니다.",
)}
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="mt-1 rounded-full border border-[var(--color-border)] p-2">
<Box size={14} />
</div>
<div>
<p className="font-semibold text-foreground">
{t(
"msg.admin.overview.playbook.tenant_title",
"Tenant isolation",
)}
</p>
<p>
{t(
"msg.admin.overview.playbook.tenant_body",
"Tenant 헤더와 감사 로그 규칙을 기본 적용하며, 향후 Keto 정책으로 확장 예정입니다.",
)}
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-[var(--color-panel)]">
<CardHeader>
<CardTitle className="text-xl">
{t("ui.admin.overview.quick_links.title", "빠른 이동")}
</CardTitle>
<CardDescription>
{t(
"msg.admin.overview.quick_links.description",
"주요 운영 화면으로 바로 이동합니다.",
)}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<Button
asChild
className="w-full justify-between"
variant="outline"
>
<Link to="/tenants/new">
{t("ui.admin.overview.quick_links.add_tenant", "테넌트 추가")}
<ArrowUpRight size={16} />
</Link>
</Button>
<Button
asChild
className="w-full justify-between"
variant="outline"
>
<Link to="/audit-logs">
{t(
"ui.admin.overview.quick_links.view_audit_logs",
"감사 로그 보기",
)}
<ArrowUpRight size={16} />
</Link>
</Button>
<Button
asChild
className="w-full justify-between"
variant="outline"
>
<Link to="/dashboard">
{t(
"ui.admin.overview.quick_links.tenant_dashboard",
"테넌트 대시보드",
)}
<ArrowUpRight size={16} />
</Link>
</Button>
</CardContent>
</Card>
</div>
<PermissionChecker />
</div>
);
}
export default GlobalOverviewPage;