forked from baron/baron-sso
타이틀 크기 및 상단 패딩 밀림 제거
This commit is contained in:
@@ -4,9 +4,9 @@ import PermissionChecker from "./components/PermissionChecker";
|
|||||||
function AuthPage() {
|
function AuthPage() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-end justify-between gap-4">
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-2">
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
<h2 className="text-3xl font-semibold">
|
||||||
{t("ui.admin.auth_guard.title", "Auth Guard")}
|
{t("ui.admin.auth_guard.title", "Auth Guard")}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -325,13 +325,13 @@ function DataIntegrityContent() {
|
|||||||
const recheckMessage = recheckStatusText(recheckStatus);
|
const recheckMessage = recheckStatusText(recheckStatus);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="space-y-6 p-6 md:p-8">
|
<main className="space-y-6">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<header className="flex flex-shrink-0 flex-wrap items-start justify-between gap-4 sticky top-[-2.5rem] z-20 -mt-4 bg-background/95 pb-2 pt-4 backdrop-blur">
|
||||||
<div>
|
<div className="space-y-2">
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
<h2 className="text-3xl font-semibold">
|
||||||
{t("ui.admin.integrity.title", "데이터 정합성 검증")}
|
{t("ui.admin.integrity.title", "데이터 정합성 검증")}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t(
|
{t(
|
||||||
"msg.admin.integrity.subtitle",
|
"msg.admin.integrity.subtitle",
|
||||||
"Review integrity status and inspect checks across the admin data model.",
|
"Review integrity status and inspect checks across the admin data model.",
|
||||||
@@ -359,176 +359,184 @@ function DataIntegrityContent() {
|
|||||||
</output>
|
</output>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
{isError ? (
|
<div className="space-y-4 pb-6">
|
||||||
<section className="rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive">
|
{isError ? (
|
||||||
{(error as Error)?.message ||
|
<section className="rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive">
|
||||||
t(
|
{(error as Error)?.message ||
|
||||||
"msg.admin.integrity.report.load_error",
|
t(
|
||||||
"정합성 리포트를 불러오지 못했습니다.",
|
"msg.admin.integrity.report.load_error",
|
||||||
)}
|
"정합성 리포트를 불러오지 못했습니다.",
|
||||||
</section>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<section className="rounded-lg border border-border bg-card p-5">
|
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border pb-4">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-base font-semibold">
|
|
||||||
{t("ui.admin.integrity.read_model.title", "Read model integrity")}
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t(
|
|
||||||
"msg.admin.integrity.read_model.description",
|
|
||||||
"Ory SoT를 덮어쓰지 않고 backend DB read model의 이상 징후만 확인합니다.",
|
|
||||||
)}
|
)}
|
||||||
</p>
|
</section>
|
||||||
</div>
|
) : null}
|
||||||
{data ? (
|
|
||||||
<Badge variant={statusBadgeVariant(data.status)}>
|
|
||||||
{statusLabel(data.status)}
|
|
||||||
</Badge>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isLoading ? (
|
<section className="rounded-lg border border-border bg-card p-5">
|
||||||
<div className="py-8 text-sm text-muted-foreground">
|
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border pb-4">
|
||||||
{t("ui.admin.integrity.loading", "불러오는 중")}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<dl className="grid gap-4 py-5 sm:grid-cols-2 lg:grid-cols-4">
|
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm text-muted-foreground">
|
|
||||||
{t("ui.admin.integrity.summary.total_checks", "검사 항목")}
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-xl font-semibold tabular-nums">
|
|
||||||
{data?.summary.totalChecks ?? 0}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt className="text-sm text-muted-foreground">
|
|
||||||
{t("ui.admin.integrity.summary.passed", "정상")}
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-xl font-semibold tabular-nums">
|
|
||||||
{data?.summary.passed ?? 0}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt className="text-sm text-muted-foreground">
|
|
||||||
{t("ui.admin.integrity.summary.failures", "실패 건수")}
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-xl font-semibold tabular-nums">
|
|
||||||
{data?.summary.failures ?? 0}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt className="text-sm text-muted-foreground">
|
|
||||||
{t("ui.admin.integrity.summary.checked_at", "검사 시각")}
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm">
|
|
||||||
{formatDateTime(data?.checkedAt)}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{(data?.sections ?? []).map((section) => (
|
|
||||||
<section
|
|
||||||
key={section.key}
|
|
||||||
className="rounded-lg border border-border bg-card p-5"
|
|
||||||
>
|
|
||||||
<div className="mb-4 flex items-center justify-between gap-3">
|
|
||||||
<h3 className="text-base font-semibold">
|
<h3 className="text-base font-semibold">
|
||||||
{integritySectionLabel(section.key, section.label)}
|
{t(
|
||||||
|
"ui.admin.integrity.read_model.title",
|
||||||
|
"Read model integrity",
|
||||||
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<Badge variant={statusBadgeVariant(section.status)}>
|
<p className="text-sm text-muted-foreground">
|
||||||
{statusLabel(section.status)}
|
{t(
|
||||||
</Badge>
|
"msg.admin.integrity.read_model.description",
|
||||||
|
"Ory SoT를 덮어쓰지 않고 backend DB read model의 이상 징후만 확인합니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="divide-y divide-border">
|
{data ? (
|
||||||
{section.checks.map((check) => (
|
<Badge variant={statusBadgeVariant(data.status)}>
|
||||||
<div
|
{statusLabel(data.status)}
|
||||||
key={check.key}
|
</Badge>
|
||||||
className="grid gap-3 py-4 md:grid-cols-[1fr_auto]"
|
) : null}
|
||||||
>
|
</div>
|
||||||
<div className="flex gap-3">
|
|
||||||
<CheckIcon check={check} />
|
{isLoading ? (
|
||||||
<div>
|
<div className="py-8 text-sm text-muted-foreground">
|
||||||
<div className="font-medium">
|
{t("ui.admin.integrity.loading", "불러오는 중")}
|
||||||
{integrityCheckLabel(check.key, check.label)}
|
</div>
|
||||||
|
) : (
|
||||||
|
<dl className="grid gap-4 py-5 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-muted-foreground">
|
||||||
|
{t("ui.admin.integrity.summary.total_checks", "검사 항목")}
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 text-xl font-semibold tabular-nums">
|
||||||
|
{data?.summary.totalChecks ?? 0}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-muted-foreground">
|
||||||
|
{t("ui.admin.integrity.summary.passed", "정상")}
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 text-xl font-semibold tabular-nums">
|
||||||
|
{data?.summary.passed ?? 0}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-muted-foreground">
|
||||||
|
{t("ui.admin.integrity.summary.failures", "실패 건수")}
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 text-xl font-semibold tabular-nums">
|
||||||
|
{data?.summary.failures ?? 0}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-muted-foreground">
|
||||||
|
{t("ui.admin.integrity.summary.checked_at", "검사 시각")}
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 text-sm">
|
||||||
|
{formatDateTime(data?.checkedAt)}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{(data?.sections ?? []).map((section) => (
|
||||||
|
<section
|
||||||
|
key={section.key}
|
||||||
|
className="rounded-lg border border-border bg-card p-5"
|
||||||
|
>
|
||||||
|
<div className="mb-4 flex items-center justify-between gap-3">
|
||||||
|
<h3 className="text-base font-semibold">
|
||||||
|
{integritySectionLabel(section.key, section.label)}
|
||||||
|
</h3>
|
||||||
|
<Badge variant={statusBadgeVariant(section.status)}>
|
||||||
|
{statusLabel(section.status)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-border">
|
||||||
|
{section.checks.map((check) => (
|
||||||
|
<div
|
||||||
|
key={check.key}
|
||||||
|
className="grid gap-3 py-4 md:grid-cols-[1fr_auto]"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<CheckIcon check={check} />
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">
|
||||||
|
{integrityCheckLabel(check.key, check.label)}
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
|
{integrityCheckDescription(
|
||||||
|
check.key,
|
||||||
|
check.description,
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
</div>
|
||||||
{integrityCheckDescription(check.key, check.description)}
|
<div className="flex items-center gap-3 md:justify-end">
|
||||||
</p>
|
<Badge variant={statusBadgeVariant(check.status)}>
|
||||||
|
{statusLabel(check.status)}
|
||||||
|
</Badge>
|
||||||
|
<span className="min-w-12 text-right text-lg font-semibold tabular-nums">
|
||||||
|
{check.count}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 md:justify-end">
|
))}
|
||||||
<Badge variant={statusBadgeVariant(check.status)}>
|
</div>
|
||||||
{statusLabel(check.status)}
|
</section>
|
||||||
</Badge>
|
))}
|
||||||
<span className="min-w-12 text-right text-lg font-semibold tabular-nums">
|
|
||||||
{check.count}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section className="rounded-lg border border-border bg-card p-5">
|
|
||||||
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-base font-semibold">
|
|
||||||
{t(
|
|
||||||
"ui.admin.integrity.orphan_login_ids.title",
|
|
||||||
"유령 로그인 ID 정리",
|
|
||||||
)}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
|
||||||
{t(
|
|
||||||
"msg.admin.integrity.orphan_login_ids.description",
|
|
||||||
"삭제되었거나 존재하지 않는 사용자/테넌트를 참조하는 로그인 ID를 확인한 뒤 선택 삭제합니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="destructive"
|
|
||||||
onClick={handleDeleteSelected}
|
|
||||||
disabled={
|
|
||||||
selectedOrphanIds.length === 0 || deleteMutation.isPending
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t("ui.admin.integrity.orphan_login_ids.delete", "선택 삭제")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
{orphanLoginIDsQuery.isError ? (
|
|
||||||
<div className="mb-3 rounded border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
|
<section className="rounded-lg border border-border bg-card p-5">
|
||||||
{t(
|
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||||
"msg.admin.integrity.orphan_login_ids.load_error",
|
<div>
|
||||||
"유령 로그인 ID 대상을 불러오지 못했습니다.",
|
<h3 className="text-base font-semibold">
|
||||||
)}
|
{t(
|
||||||
|
"ui.admin.integrity.orphan_login_ids.title",
|
||||||
|
"유령 로그인 ID 정리",
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
"msg.admin.integrity.orphan_login_ids.description",
|
||||||
|
"삭제되었거나 존재하지 않는 사용자/테넌트를 참조하는 로그인 ID를 확인한 뒤 선택 삭제합니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleDeleteSelected}
|
||||||
|
disabled={
|
||||||
|
selectedOrphanIds.length === 0 || deleteMutation.isPending
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("ui.admin.integrity.orphan_login_ids.delete", "선택 삭제")}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
{orphanLoginIDsQuery.isError ? (
|
||||||
{deleteMutation.data ? (
|
<div className="mb-3 rounded border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
|
||||||
<div className="mb-3 rounded border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-200">
|
{t(
|
||||||
{t(
|
"msg.admin.integrity.orphan_login_ids.load_error",
|
||||||
"msg.admin.integrity.orphan_login_ids.delete_success",
|
"유령 로그인 ID 대상을 불러오지 못했습니다.",
|
||||||
"{{count}}개의 유령 로그인 ID를 삭제했습니다.",
|
)}
|
||||||
{ count: deleteMutation.data.deletedCount },
|
</div>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
{deleteMutation.data ? (
|
||||||
) : null}
|
<div className="mb-3 rounded border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-200">
|
||||||
<OrphanLoginIDTable
|
{t(
|
||||||
items={orphanItems}
|
"msg.admin.integrity.orphan_login_ids.delete_success",
|
||||||
selectedIds={selectedOrphanIds}
|
"{{count}}개의 유령 로그인 ID를 삭제했습니다.",
|
||||||
onToggle={toggleOrphanID}
|
{ count: deleteMutation.data.deletedCount },
|
||||||
/>
|
)}
|
||||||
</section>
|
</div>
|
||||||
|
) : null}
|
||||||
|
<OrphanLoginIDTable
|
||||||
|
items={orphanItems}
|
||||||
|
selectedIds={selectedOrphanIds}
|
||||||
|
onToggle={toggleOrphanID}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ function IntegrityOverviewSummary() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="border-t border-border/60 pt-4">
|
<section className="border-t border-border/60 pt-4">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{data.status === "pass" ? (
|
{data.status === "pass" ? (
|
||||||
<CheckCircle2 size={18} className="text-emerald-600" />
|
<CheckCircle2 size={18} className="text-emerald-600" />
|
||||||
@@ -287,7 +287,7 @@ function RPUsageMixedChart({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-base font-semibold">
|
<h3 className="text-base font-semibold">
|
||||||
{t("ui.admin.overview.chart.title", "회사별 앱별 로그인 요청 현황")}
|
{t("ui.admin.overview.chart.title", "회사별 앱별 로그인 요청 현황")}
|
||||||
@@ -507,9 +507,9 @@ function GlobalOverviewPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 animate-in fade-in duration-500">
|
<div className="space-y-4 animate-in fade-in duration-500">
|
||||||
<div className="flex flex-wrap items-end justify-between gap-4">
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
<h2 className="text-3xl font-semibold">
|
||||||
{t("ui.common.overview.title", "운영 현황")}
|
{t("ui.common.overview.title", "운영 현황")}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
@@ -562,7 +562,7 @@ function GlobalOverviewPage() {
|
|||||||
|
|
||||||
{usageQuery.isError ? (
|
{usageQuery.isError ? (
|
||||||
<section className="space-y-2">
|
<section className="space-y-2">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-base font-semibold">
|
<h3 className="text-base font-semibold">
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
@@ -95,16 +95,16 @@ function UserProjectionContent() {
|
|||||||
const actionError = reconcileMutation.error ?? resetMutation.error;
|
const actionError = reconcileMutation.error ?? resetMutation.error;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="space-y-6 p-6 md:p-8">
|
<main className="space-y-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<header className="flex flex-shrink-0 flex-wrap items-start justify-between gap-4 sticky top-[-2.5rem] z-20 -mt-4 bg-background/95 pb-2 pt-4 backdrop-blur">
|
||||||
<div>
|
<div className="space-y-2">
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
<h2 className="text-3xl font-semibold">
|
||||||
{t(
|
{t(
|
||||||
"ui.admin.user_projection.title",
|
"ui.admin.user_projection.title",
|
||||||
"User Projection Management",
|
"User Projection Management",
|
||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t(
|
{t(
|
||||||
"msg.admin.user_projection.subtitle",
|
"msg.admin.user_projection.subtitle",
|
||||||
"Review and sync the Kratos user read model.",
|
"Review and sync the Kratos user read model.",
|
||||||
@@ -134,7 +134,7 @@ function UserProjectionContent() {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<section className="rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive">
|
<section className="rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive">
|
||||||
|
|||||||
Reference in New Issue
Block a user