From 6887a2b4fca188803b3b1721cbd9c0c56b42c1f4 Mon Sep 17 00:00:00 2001 From: htlee Date: Thu, 9 Apr 2026 11:32:03 +0900 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=97=B0=EA=B2=B0=20Step=204=20=E2=80=94?= =?UTF-8?q?=20Enforcement=20=EC=97=B0=EA=B3=84=20+=20admin=20=EC=84=9C?= =?UTF-8?q?=EB=B8=8C=EA=B7=B8=EB=A3=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EnforcementHistory: - eventId 역추적 컬럼 추가 (#{eventId} 클릭 → EventList 이동) - Record 인터페이스에 eventId 필드 추가 EnforcementPlan: - 미배정 CRITICAL 이벤트 패널 신설 (NEW 상태 CRITICAL 이벤트 표시) - getEvents(level=CRITICAL, status=NEW) 연동 MainLayout: - admin 메뉴 4개 서브그룹 분리 (AI 플랫폼/시스템 운영/사용자 관리/감사·보안) - NavDivider 타입 도입으로 그룹 내 소제목 라벨 렌더링 - 기존 RBAC 필터링 + collapsed 모드 호환 유지 Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/app/layout/MainLayout.tsx | 44 ++++++++++++++----- .../enforcement/EnforcementHistory.tsx | 19 ++++++++ .../risk-assessment/EnforcementPlan.tsx | 44 +++++++++++++++++-- 3 files changed, 91 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/layout/MainLayout.tsx b/frontend/src/app/layout/MainLayout.tsx index 8159ce6..bb3559d 100644 --- a/frontend/src/app/layout/MainLayout.tsx +++ b/frontend/src/app/layout/MainLayout.tsx @@ -35,10 +35,12 @@ const AUTH_METHOD_LABELS: Record = { }; interface NavItem { to: string; icon: React.ElementType; labelKey: string; } -interface NavGroup { groupKey: string; icon: React.ElementType; items: NavItem[]; } +interface NavDivider { dividerLabel: string; } +interface NavGroup { groupKey: string; icon: React.ElementType; items: (NavItem | NavDivider)[]; } type NavEntry = NavItem | NavGroup; const isGroup = (entry: NavEntry): entry is NavGroup => 'groupKey' in entry; +const isDivider = (item: NavItem | NavDivider): item is NavDivider => 'dividerLabel' in item; const NAV_ENTRIES: NavEntry[] = [ // ── 상황판·감시 ── @@ -82,16 +84,20 @@ const NAV_ENTRIES: NavEntry[] = [ { groupKey: 'group.admin', icon: Settings, items: [ + { dividerLabel: 'AI 플랫폼' }, { to: '/ai-model', icon: Brain, labelKey: 'nav.aiModel' }, { to: '/mlops', icon: Cpu, labelKey: 'nav.mlops' }, { to: '/llm-ops', icon: Brain, labelKey: 'nav.llmOps' }, { to: '/ai-assistant', icon: MessageSquare, labelKey: 'nav.aiAssistant' }, - { to: '/external-service', icon: Globe, labelKey: 'nav.externalService' }, - { to: '/data-hub', icon: Wifi, labelKey: 'nav.dataHub' }, + { dividerLabel: '시스템 운영' }, { to: '/system-config', icon: Database, labelKey: 'nav.systemConfig' }, - { to: '/notices', icon: Megaphone, labelKey: 'nav.notices' }, + { to: '/data-hub', icon: Wifi, labelKey: 'nav.dataHub' }, + { to: '/external-service', icon: Globe, labelKey: 'nav.externalService' }, + { dividerLabel: '사용자 관리' }, { to: '/admin', icon: Settings, labelKey: 'nav.admin' }, { to: '/access-control', icon: Fingerprint, labelKey: 'nav.accessControl' }, + { to: '/notices', icon: Megaphone, labelKey: 'nav.notices' }, + { dividerLabel: '감사·보안' }, { to: '/admin/audit-logs', icon: ScrollText, labelKey: 'nav.auditLogs' }, { to: '/admin/access-logs', icon: History, labelKey: 'nav.accessLogs' }, { to: '/admin/login-history', icon: KeyRound, labelKey: 'nav.loginHistory' }, @@ -99,8 +105,10 @@ const NAV_ENTRIES: NavEntry[] = [ }, ]; -// getPageLabel용 flat 목록 -const NAV_ITEMS = NAV_ENTRIES.flatMap(e => isGroup(e) ? e.items : [e]); +// getPageLabel용 flat 목록 (divider 제외) +const NAV_ITEMS = NAV_ENTRIES.flatMap(e => + isGroup(e) ? e.items.filter((i): i is NavItem => !isDivider(i)) : [e] +); function formatRemaining(seconds: number) { const m = Math.floor(seconds / 60); @@ -255,11 +263,12 @@ export function MainLayout() {