From 63622dcf2822c3fad01e303258568884c81ee559 Mon Sep 17 00:00:00 2001 From: kyy Date: Tue, 19 May 2026 15:52:17 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B0=95=EC=8A=A4=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=ED=8B=80=20description=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A1=9C=EA=B3=A0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../src/features/api-keys/ApiKeyListPage.tsx | 1 + .../src/features/audit/AuditLogsPage.tsx | 10 +- adminfront/src/features/auth/AuthPage.tsx | 8 +- .../auth/components/PermissionChecker.tsx | 7 +- .../features/integrity/DataIntegrityPage.tsx | 53 ++- .../features/overview/GlobalOverviewPage.tsx | 26 +- .../projections/UserProjectionPage.tsx | 33 +- .../tenants/routes/TenantListPage.tsx | 301 ++++++++---------- .../src/features/users/UserListPage.tsx | 3 +- adminfront/src/locales/en.toml | 7 + adminfront/src/locales/ko.toml | 7 + adminfront/src/test/i18nMock.ts | 12 + common/core/components/page/PageHeader.tsx | 31 +- 14 files changed, 271 insertions(+), 229 deletions(-) diff --git a/.gitignore b/.gitignore index 4f606658..f4f54941 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ reports reports/* config/*.pem common/node_modules +common/.baron-deps-install.lock # Docker Services Data (Volumes) postgres_data/ diff --git a/adminfront/src/features/api-keys/ApiKeyListPage.tsx b/adminfront/src/features/api-keys/ApiKeyListPage.tsx index c64b3fe6..0a95fc68 100644 --- a/adminfront/src/features/api-keys/ApiKeyListPage.tsx +++ b/adminfront/src/features/api-keys/ApiKeyListPage.tsx @@ -164,6 +164,7 @@ function ApiKeyListPage() { } title={t("ui.admin.api_keys.list.title", "API 키 관리 (M2M)")} description={t( "msg.admin.api_keys.list.subtitle", diff --git a/adminfront/src/features/audit/AuditLogsPage.tsx b/adminfront/src/features/audit/AuditLogsPage.tsx index 71b83490..d55540ba 100644 --- a/adminfront/src/features/audit/AuditLogsPage.tsx +++ b/adminfront/src/features/audit/AuditLogsPage.tsx @@ -1,6 +1,6 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import type { AxiosError } from "axios"; -import { Download, RefreshCw, Search } from "lucide-react"; +import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react"; import * as React from "react"; import { formatAuditValue, @@ -16,6 +16,7 @@ import { Button } from "../../components/ui/button"; import { Card, CardContent, + CardDescription, CardHeader, CardTitle, } from "../../components/ui/card"; @@ -99,6 +100,7 @@ function AuditLogsPage() { "msg.admin.audit.subtitle", "관리자 작업 이력을 조회합니다.", )} + icon={} actions={ <> @@ -128,6 +130,12 @@ function AuditLogsPage() { {t("ui.common.audit.registry.title", "Audit registry")} + + {t( + "msg.admin.audit.registry.description", + "최근 감사 로그를 검색 조건에 맞춰 필터링하고, 작업 이력을 빠르게 확인합니다.", + )} + diff --git a/adminfront/src/features/auth/AuthPage.tsx b/adminfront/src/features/auth/AuthPage.tsx index 39fa62c6..8985b290 100644 --- a/adminfront/src/features/auth/AuthPage.tsx +++ b/adminfront/src/features/auth/AuthPage.tsx @@ -1,3 +1,4 @@ +import { ShieldHalf } from "lucide-react"; import { t } from "../../lib/i18n"; import PermissionChecker from "./components/PermissionChecker"; @@ -6,8 +7,11 @@ function AuthPage() {
-

- {t("ui.admin.auth_guard.title", "Auth Guard")} +

+ + + + {t("ui.admin.auth_guard.title", "Auth Guard")}

{t( diff --git a/adminfront/src/features/auth/components/PermissionChecker.tsx b/adminfront/src/features/auth/components/PermissionChecker.tsx index 2be914f1..c8dbba7b 100644 --- a/adminfront/src/features/auth/components/PermissionChecker.tsx +++ b/adminfront/src/features/auth/components/PermissionChecker.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import { CheckCircle2, XCircle } from "lucide-react"; +import { CheckCircle2, ShieldCheck, XCircle } from "lucide-react"; import { useState } from "react"; import { Button } from "../../../components/ui/button"; import { @@ -47,7 +47,10 @@ function PermissionChecker() { return ( - + + + + {t( "ui.admin.auth_guard.checker.title", "ReBAC permission checker", diff --git a/adminfront/src/features/integrity/DataIntegrityPage.tsx b/adminfront/src/features/integrity/DataIntegrityPage.tsx index bd8988df..7f449d28 100644 --- a/adminfront/src/features/integrity/DataIntegrityPage.tsx +++ b/adminfront/src/features/integrity/DataIntegrityPage.tsx @@ -90,6 +90,23 @@ function integritySectionLabel(key: string, fallback: string) { } } +function integritySectionDescription(key: string) { + switch (key) { + case "tenant_integrity": + return t( + "msg.admin.integrity.section.tenant_integrity.description", + "테넌트 slug 중복과 부모 관계 이상을 확인합니다.", + ); + case "user_integrity": + return t( + "msg.admin.integrity.section.user_integrity.description", + "사용자와 로그인 ID 참조의 고아 레코드를 확인합니다.", + ); + default: + return ""; + } +} + function integrityCheckLabel(key: string, fallback: string) { switch (key) { case "duplicate_tenant_slugs": @@ -327,16 +344,21 @@ function DataIntegrityContent() { return (

-
-

- {t("ui.admin.integrity.title", "데이터 정합성 검증")} -

-

- {t( - "msg.admin.integrity.subtitle", - "Review integrity status and inspect checks across the admin data model.", - )} -

+
+
+ +
+
+

+ {t("ui.admin.integrity.title", "데이터 정합성 검증")} +

+

+ {t( + "msg.admin.integrity.subtitle", + "Review integrity status and inspect checks across the admin data model.", + )} +

+
- -
- - {scopeTenantId && ( - - )} - - - - - - - - - - - {t("ui.admin.tenants.csv_template", "템플릿 다운로드")} - - - fileInputRef.current?.click()} - disabled={importMutation.isPending} - data-testid="tenant-import-menu-item" - className="cursor-pointer" - > - - {t("ui.admin.tenants.import", "CSV 가져오기")} - - - exportMutation.mutate(false)} - disabled={exportMutation.isPending} - data-testid="tenant-export-menu-item" - className="cursor-pointer" - > - - {t( - "ui.admin.tenants.export_without_ids", - "UUID 제외 내보내기", - )} - - exportMutation.mutate(true)} - disabled={exportMutation.isPending} - data-testid="tenant-export-with-ids-menu-item" - className="cursor-pointer" - > - - {t("ui.admin.tenants.export_with_ids", "UUID 포함 내보내기")} - - - - - - - - - -
- {importMessage && ( -
- {importMessage} -
+ } + title={t("ui.admin.tenants.title", "테넌트 목록")} + description={t( + "msg.admin.tenants.subtitle", + "시스템에 등록된 모든 테넌트를 평면 목록으로 확인하고 관리합니다.", )} -
+ actions={ + <> +
+
+ + setSearch(e.target.value)} + /> +
+ + + + + + + + + + + {t("ui.admin.tenants.csv_template", "템플릿 다운로드")} + + + fileInputRef.current?.click()} + disabled={importMutation.isPending} + data-testid="tenant-import-menu-item" + className="cursor-pointer" + > + + {t("ui.admin.tenants.import", "CSV 가져오기")} + + + exportMutation.mutate(false)} + disabled={exportMutation.isPending} + data-testid="tenant-export-menu-item" + className="cursor-pointer" + > + + {t( + "ui.admin.tenants.export_without_ids", + "UUID 제외 내보내기", + )} + + exportMutation.mutate(true)} + disabled={exportMutation.isPending} + data-testid="tenant-export-with-ids-menu-item" + className="cursor-pointer" + > + + {t( + "ui.admin.tenants.export_with_ids", + "UUID 포함 내보내기", + )} + + + + + + + + + +
+ {importMessage ? ( +
+ {importMessage} +
+ ) : null} + + } + /> diff --git a/adminfront/src/features/users/UserListPage.tsx b/adminfront/src/features/users/UserListPage.tsx index 3217303a..fb4f5cb8 100644 --- a/adminfront/src/features/users/UserListPage.tsx +++ b/adminfront/src/features/users/UserListPage.tsx @@ -7,8 +7,8 @@ import { ChevronDown, ChevronLeft, ChevronRight, + Users, FileDown, - LayoutDashboard, Plus, RefreshCw, Search, @@ -416,6 +416,7 @@ function UserListPage() { } title={ {t("ui.admin.users.list.title", "사용자 관리")} diff --git a/adminfront/src/locales/en.toml b/adminfront/src/locales/en.toml index 7e34649c..1c12bd43 100644 --- a/adminfront/src/locales/en.toml +++ b/adminfront/src/locales/en.toml @@ -114,6 +114,7 @@ empty = "Empty" [msg.admin.audit.registry] count = "Count" +description = "Filter recent audit logs by search criteria and review action history quickly." [msg.admin.groups] @@ -937,6 +938,12 @@ user = "User" tenant_integrity = "Tenant integrity" user_integrity = "User integrity" +[msg.admin.integrity.section.tenant_integrity] +description = "Checks duplicate tenant slugs and orphan parent relationships." + +[msg.admin.integrity.section.user_integrity] +description = "Checks orphan records for users and login ID references." + [ui.admin.integrity.check.duplicate_tenant_slugs] title = "Duplicate tenant slug" diff --git a/adminfront/src/locales/ko.toml b/adminfront/src/locales/ko.toml index bc4b623b..0b2d4aa0 100644 --- a/adminfront/src/locales/ko.toml +++ b/adminfront/src/locales/ko.toml @@ -114,6 +114,7 @@ empty = "필터 없음" [msg.admin.audit.registry] count = "로드된 로그 {{count}}건" +description = "최근 감사 로그를 검색 조건에 맞춰 필터링하고, 작업 이력을 빠르게 확인합니다." [msg.admin.groups] @@ -941,6 +942,12 @@ user = "사용자" tenant_integrity = "테넌트 정합성" user_integrity = "사용자 정합성" +[msg.admin.integrity.section.tenant_integrity] +description = "테넌트 slug 중복과 부모 관계 이상을 확인합니다." + +[msg.admin.integrity.section.user_integrity] +description = "사용자와 로그인 ID 참조의 고아 레코드를 확인합니다." + [ui.admin.integrity.check.duplicate_tenant_slugs] title = "중복 테넌트 slug" diff --git a/adminfront/src/test/i18nMock.ts b/adminfront/src/test/i18nMock.ts index b15c6f2e..7c15c3cd 100644 --- a/adminfront/src/test/i18nMock.ts +++ b/adminfront/src/test/i18nMock.ts @@ -30,6 +30,12 @@ const translations: Record<"ko" | "en", Record> = { "ui.admin.integrity.title": "데이터 정합성 검증", "msg.admin.integrity.subtitle": "정합성 상태를 확인하고 데이터 모델 전반의 검증 결과를 살펴봅니다.", + "msg.admin.integrity.section.tenant_integrity.description": + "테넌트 slug 중복과 부모 관계 이상을 확인합니다.", + "msg.admin.integrity.section.user_integrity.description": + "사용자와 로그인 ID 참조의 고아 레코드를 확인합니다.", + "msg.admin.audit.registry.description": + "최근 감사 로그를 검색 조건에 맞춰 필터링하고, 작업 이력을 빠르게 확인합니다.", "ui.admin.integrity.recheck.run": "다시 검사", "ui.admin.integrity.recheck.running": "검사 중", "ui.admin.integrity.status.fail": "실패", @@ -103,6 +109,12 @@ const translations: Record<"ko" | "en", Record> = { "ui.admin.integrity.title": "Data Integrity Check", "msg.admin.integrity.subtitle": "Review integrity status and inspect checks across the admin data model.", + "msg.admin.integrity.section.tenant_integrity.description": + "Checks duplicate tenant slugs and orphan parent relationships.", + "msg.admin.integrity.section.user_integrity.description": + "Checks orphan records for users and login ID references.", + "msg.admin.audit.registry.description": + "Filter recent audit logs by search criteria and review action history quickly.", "ui.admin.integrity.recheck.run": "Run again", "ui.admin.integrity.recheck.running": "Checking", "ui.admin.integrity.status.fail": "Failed", diff --git a/common/core/components/page/PageHeader.tsx b/common/core/components/page/PageHeader.tsx index 537f76f7..c574079a 100644 --- a/common/core/components/page/PageHeader.tsx +++ b/common/core/components/page/PageHeader.tsx @@ -6,6 +6,7 @@ function cx(...classNames: Array) { type PageHeaderProps = Omit, "title"> & { actions?: ReactNode; + icon?: ReactNode; as?: ElementType; description?: ReactNode; eyebrow?: ReactNode; @@ -20,6 +21,7 @@ export function PageHeader({ className, description, eyebrow, + icon, sticky = false, title, titleAs, @@ -38,18 +40,25 @@ export function PageHeader({ )} {...props} > -
- {eyebrow ? ( -

- {eyebrow} -

- ) : null} - - {title} - - {description ? ( -

{description}

+
+ {icon ? ( +
+ {icon} +
) : null} +
+ {eyebrow ? ( +

+ {eyebrow} +

+ ) : null} + + {title} + + {description ? ( +

{description}

+ ) : null} +
{actions ? (
{actions}