feat: 시스템관리 > 감사·보안에 데이터 보관·파기 정책(DAR-10) 메뉴 추가
- 5탭 구성: 보관 현황 / 유형별 보관기간 / 파기 절차 / 예외·연장 / 파기 감사 대장 - 6종 데이터 유형별 보관기간 기준표 (법적 근거 포함) - 4단계 파기 승인 절차 워크플로우 (선별→신청→승인→기록) - 보존 연장 예외 관리 (수사·소송·감사·재난 4가지 사유) - 파기 감사 대장 (대상·일시·담당자·방식·용량 기록) - V026 마이그레이션: admin:data-retention 권한 트리 + ADMIN 역할 권한 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
8fe55474d9
커밋
615871a45f
@ -0,0 +1,28 @@
|
||||
-- ============================================================
|
||||
-- V026: 데이터 보관기간 및 파기 정책 (DAR-10) 메뉴 추가
|
||||
-- 시스템관리 > 감사·보안 서브그룹
|
||||
-- ============================================================
|
||||
|
||||
-- 1. 권한 트리 노드 등록
|
||||
INSERT INTO kcg.auth_perm_tree(rsrc_cd, parent_cd, rsrc_nm, rsrc_level, sort_ord)
|
||||
VALUES ('admin:data-retention', 'admin', '데이터 보관·파기', 1, 57)
|
||||
ON CONFLICT (rsrc_cd) DO NOTHING;
|
||||
|
||||
-- 2. 메뉴 메타데이터 갱신
|
||||
UPDATE kcg.auth_perm_tree
|
||||
SET url_path = '/admin/data-retention',
|
||||
label_key = 'nav.dataRetentionPolicy',
|
||||
component_key = 'features/admin/DataRetentionPolicy',
|
||||
nav_group = 'admin',
|
||||
nav_sub_group = '감사·보안',
|
||||
nav_sort = 2000,
|
||||
labels = '{"ko":"데이터 보관·파기","en":"Data Retention"}'
|
||||
WHERE rsrc_cd = 'admin:data-retention';
|
||||
|
||||
-- 3. ADMIN 역할에 전체 권한 부여
|
||||
INSERT INTO kcg.auth_perm(role_sn, rsrc_cd, oper_cd, grant_yn)
|
||||
SELECT r.role_sn, 'admin:data-retention', op.oper_cd, 'Y'
|
||||
FROM kcg.auth_role r
|
||||
CROSS JOIN (VALUES ('READ'), ('CREATE'), ('UPDATE'), ('DELETE'), ('EXPORT')) AS op(oper_cd)
|
||||
WHERE r.role_cd = 'ADMIN'
|
||||
ON CONFLICT (role_sn, rsrc_cd, oper_cd) DO NOTHING;
|
||||
@ -122,6 +122,9 @@ export const COMPONENT_REGISTRY: Record<string, LazyComponent> = {
|
||||
'features/admin/AIAgentSecurityPage': lazy(() =>
|
||||
import('@features/admin').then((m) => ({ default: m.AIAgentSecurityPage })),
|
||||
),
|
||||
'features/admin/DataRetentionPolicy': lazy(() =>
|
||||
import('@features/admin').then((m) => ({ default: m.DataRetentionPolicy })),
|
||||
),
|
||||
// ── 모선 워크플로우 ──
|
||||
'features/parent-inference/ParentReview': lazy(() =>
|
||||
import('@features/parent-inference/ParentReview').then((m) => ({
|
||||
|
||||
432
frontend/src/features/admin/DataRetentionPolicy.tsx
Normal file
432
frontend/src/features/admin/DataRetentionPolicy.tsx
Normal file
@ -0,0 +1,432 @@
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent } from '@shared/components/ui/card';
|
||||
import { Badge } from '@shared/components/ui/badge';
|
||||
import { PageContainer, PageHeader } from '@shared/components/layout';
|
||||
import { getStatusIntent } from '@shared/constants/statusIntent';
|
||||
import {
|
||||
Database, Clock, Trash2, ShieldCheck, FileText, AlertTriangle,
|
||||
CheckCircle, Archive, CalendarClock, UserCheck, Search,
|
||||
ChevronRight, Lock, Eye, Settings,
|
||||
} from 'lucide-react';
|
||||
|
||||
/*
|
||||
* DAR-10: 데이터 보관기간 및 파기 정책
|
||||
*
|
||||
* 법령 내 규정 및 유관기관 협의에 따라 데이터 유형별 보관기간 및
|
||||
* 파기 절차를 수립하고 체계적으로 관리하는 정책 페이지.
|
||||
*
|
||||
* ① 보관 현황 ② 유형별 보관기간 ③ 파기 절차 ④ 예외·연장 ⑤ 파기 감사 대장
|
||||
*/
|
||||
|
||||
type Tab = 'overview' | 'retention' | 'disposal' | 'exception' | 'audit';
|
||||
|
||||
// ─── 보관 현황 KPI ──────────────────
|
||||
const RETENTION_KPI = [
|
||||
{ label: '관리 데이터 유형', value: '6종', icon: Database, color: '#3b82f6' },
|
||||
{ label: '보관기간 초과', value: '0건', icon: Clock, color: '#10b981' },
|
||||
{ label: '파기 대기', value: '3건', icon: Trash2, color: '#f59e0b' },
|
||||
{ label: '보존 연장 중', value: '1건', icon: ShieldCheck, color: '#8b5cf6' },
|
||||
{ label: '금월 파기 완료', value: '12건', icon: CheckCircle, color: '#06b6d4' },
|
||||
];
|
||||
|
||||
// ─── 유형별 보관기간 기준표 ──────────────────
|
||||
const RETENTION_TABLE = [
|
||||
{ type: '선박 위치 로그 (AIS)', category: '운항 데이터', basis: '해사안전법 시행규칙 제42조', period: '5년', format: 'PostgreSQL + TimescaleDB', volume: '약 2.1TB/년', status: '정상' },
|
||||
{ type: '단속 자료', category: '법집행 기록', basis: '해양경비법 제18조, 공공기록물법', period: '10년 (영구 가능)', format: 'PostgreSQL + 파일 스토리지', volume: '약 150GB/년', status: '정상' },
|
||||
{ type: '수사 관련 자료', category: '수사 기록', basis: '형사소송법 제198조, 수사기록 보존규칙', period: '영구 (종결 후 30년)', format: '암호화 스토리지', volume: '약 50GB/년', status: '정상' },
|
||||
{ type: 'AI 학습용 임시 데이터', category: 'AI 학습 데이터', basis: '개인정보보호법 제21조', period: '학습 완료 후 90일', format: 'S3 + DVC', volume: '약 500GB/주기', status: '정상' },
|
||||
{ type: 'CCTV·영상 증거', category: '영상 데이터', basis: '개인정보보호법 제25조', period: '30일 (증거 채택 시 영구)', format: 'NAS + HLS', volume: '약 3TB/월', status: '주의' },
|
||||
{ type: '시스템 접근 로그', category: '감사 로그', basis: '정보통신망법 제45조, 전자금융감독규정', period: '5년', format: 'Elasticsearch', volume: '약 80GB/년', status: '정상' },
|
||||
];
|
||||
|
||||
// ─── 파기 방식 정의 ──────────────────
|
||||
const DISPOSAL_METHODS = [
|
||||
{ method: '완전 삭제 (Secure Erase)', desc: 'DoD 5220.22-M 기준 3회 덮어쓰기 후 삭제', target: 'DB 레코드, 파일', encryption: '해당 없음', recovery: '복구 불가능', status: '적용' },
|
||||
{ method: '암호화 키 폐기', desc: '암호화된 데이터의 복호화 키를 영구 폐기하여 접근 차단', target: '암호화 스토리지', encryption: 'AES-256 키 폐기', recovery: '복구 불가능', status: '적용' },
|
||||
{ method: '논리적 삭제 + 만료', desc: 'soft-delete 마킹 후 보관기간 만료 시 물리 삭제 전환', target: '운영 DB', encryption: '-', recovery: '만료 전 복구 가능', status: '적용' },
|
||||
{ method: '물리적 파기', desc: '디가우저(Degausser) 또는 물리적 파쇄로 매체 파기', target: '이동식 매체, 하드디스크', encryption: '해당 없음', recovery: '복구 불가능', status: '적용' },
|
||||
];
|
||||
|
||||
// ─── 파기 승인 절차 ──────────────────
|
||||
const DISPOSAL_WORKFLOW = [
|
||||
{ phase: '① 파기 대상 선별', actions: ['보관기간 만료 데이터 자동 탐색', '유형별 파기 대상 목록 생성', '백업 데이터 포함 여부 확인'], responsible: '시스템 자동', icon: Search },
|
||||
{ phase: '② 파기 신청', actions: ['파기 대상 목록 검토 및 승인 요청', '수사·소송 보존 연장 대상 제외 확인', '파기 방식 지정 (완전삭제/키폐기/물리파기)'], responsible: '데이터 관리자', icon: FileText },
|
||||
{ phase: '③ 승인 및 집행', actions: ['보안담당관 파기 승인', '파기 실행 (이중 확인 절차)', '백업 데이터 동시 파기'], responsible: '보안담당관', icon: UserCheck },
|
||||
{ phase: '④ 결과 기록', actions: ['파기 결과 로그 자동 기록', '파기 대장 등록 (대상·일시·담당자·방식)', '감사 보고서 생성 및 보관'], responsible: '시스템 자동', icon: Archive },
|
||||
];
|
||||
|
||||
// ─── 보존 연장 예외 현황 ──────────────────
|
||||
const EXCEPTIONS = [
|
||||
{ id: 'EXC-2026-001', dataType: '단속 자료 #2024-1892', reason: '수사 진행 중 (인천해경)', originalExpiry: '2026-03-15', extendedTo: '수사 종결 시까지', approver: '수사과장', status: '연장 중' },
|
||||
{ id: 'EXC-2026-002', dataType: 'AIS 로그 (2021-Q2)', reason: '재판 증거 제출 (서울중앙지법)', originalExpiry: '2026-06-30', extendedTo: '판결 확정 시까지', approver: '법무담당관', status: '연장 중' },
|
||||
{ id: 'EXC-2025-015', dataType: 'CCTV 영상 #V-2025-0342', reason: '감사원 감사 대상', originalExpiry: '2025-12-01', extendedTo: '2026-06-30', approver: '감사담당관', status: '해제 예정' },
|
||||
];
|
||||
|
||||
const EXCEPTION_RULES = [
|
||||
{ rule: '수사·소송 보존', desc: '수사 개시 또는 소송 진행 중인 데이터는 종결 시까지 파기 유예', authority: '수사과장 / 법무담당관' },
|
||||
{ rule: '감사 보존', desc: '내부·외부 감사 대상 데이터는 감사 완료 후 6개월까지 보존 연장', authority: '감사담당관' },
|
||||
{ rule: '재난·사고 보존', desc: '해양 사고 관련 데이터는 사고 조사 종결 시까지 보존', authority: '안전관리관' },
|
||||
{ rule: '정보공개 청구', desc: '정보공개 청구 접수된 데이터는 처리 완료 시까지 보존', authority: '정보공개담당관' },
|
||||
];
|
||||
|
||||
// ─── 파기 감사 대장 ──────────────────
|
||||
const DISPOSAL_AUDIT_LOG = [
|
||||
{ id: 'DSP-2026-012', date: '2026-04-10', target: 'AI 학습 임시 데이터 (배치 #B-0392)', type: 'AI 학습 데이터', method: '완전 삭제', volume: '48.2GB', operator: '정해진', approver: '김영수', result: '완료' },
|
||||
{ id: 'DSP-2026-011', date: '2026-04-08', target: 'CCTV 영상 2026-03월분 (미채택)', type: '영상 데이터', method: '완전 삭제', volume: '2.8TB', operator: '시스템', approver: '김영수', result: '완료' },
|
||||
{ id: 'DSP-2026-010', date: '2026-04-05', target: '시스템 접근 로그 (2021-Q1)', type: '감사 로그', method: '완전 삭제', volume: '12.5GB', operator: '시스템', approver: '김영수', result: '완료' },
|
||||
{ id: 'DSP-2026-009', date: '2026-04-03', target: 'AI 학습 임시 데이터 (배치 #B-0391)', type: 'AI 학습 데이터', method: '암호화 키 폐기', volume: '51.7GB', operator: '정해진', approver: '김영수', result: '완료' },
|
||||
{ id: 'DSP-2026-008', date: '2026-04-01', target: 'AIS 위치 로그 (2021-03)', type: '운항 데이터', method: '완전 삭제', volume: '180GB', operator: '시스템', approver: '이상호', result: '완료' },
|
||||
{ id: 'DSP-2026-007', date: '2026-03-28', target: '이동식 매체 (USB-0021~0025)', type: '물리 매체', method: '물리적 파기', volume: '5개', operator: '박민수', approver: '김영수', result: '완료' },
|
||||
];
|
||||
|
||||
// ─── 보관 구조 요약 ──────────────────
|
||||
const STORAGE_ARCHITECTURE = [
|
||||
{ tier: '운영 스토리지', desc: '실시간 조회·분석 대상 (최근 1년)', tech: 'PostgreSQL + TimescaleDB', encryption: 'TDE (AES-256)', backup: '일일 증분 + 주간 전체', icon: Database },
|
||||
{ tier: '아카이브 스토리지', desc: '장기 보관 대상 (1~10년)', tech: 'S3 Compatible (Glacier 등급)', encryption: 'SSE-KMS', backup: '월간 무결성 검증', icon: Archive },
|
||||
{ tier: '백업 스토리지', desc: '재해 복구용 (이중화)', tech: '원격지 NAS + 테이프', encryption: 'AES-256', backup: '분기별 복구 테스트', icon: Lock },
|
||||
{ tier: '파기 대기 영역', desc: 'soft-delete 후 파기 승인 대기', tech: '격리 스토리지 (접근 제한)', encryption: 'AES-256', backup: '미백업 (파기 예정)', icon: Trash2 },
|
||||
];
|
||||
|
||||
export function DataRetentionPolicy() {
|
||||
const [tab, setTab] = useState<Tab>('overview');
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
icon={Database}
|
||||
iconColor="text-blue-400"
|
||||
title="데이터 보관기간 및 파기 정책"
|
||||
description="DAR-10 | 데이터 유형별 보관기간 기준표, 파기 절차, 보존 연장 예외 관리"
|
||||
demo
|
||||
/>
|
||||
|
||||
{/* 탭 */}
|
||||
<div className="flex gap-0 border-b border-border">
|
||||
{([
|
||||
{ key: 'overview' as Tab, icon: Eye, label: '보관 현황' },
|
||||
{ key: 'retention' as Tab, icon: CalendarClock, label: '유형별 보관기간' },
|
||||
{ key: 'disposal' as Tab, icon: Trash2, label: '파기 절차' },
|
||||
{ key: 'exception' as Tab, icon: ShieldCheck, label: '예외·연장' },
|
||||
{ key: 'audit' as Tab, icon: FileText, label: '파기 감사 대장' },
|
||||
]).map(t => (
|
||||
<button type="button" key={t.key} onClick={() => setTab(t.key)}
|
||||
className={`flex items-center gap-1.5 px-4 py-2.5 text-[11px] font-medium border-b-2 transition-colors ${tab === t.key ? 'text-blue-400 border-blue-400' : 'text-hint border-transparent hover:text-label'}`}>
|
||||
<t.icon className="w-3.5 h-3.5" />{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* ── ① 보관 현황 ── */}
|
||||
{tab === 'overview' && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
{RETENTION_KPI.map(k => (
|
||||
<div key={k.label} className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border bg-card" style={{ borderLeftColor: k.color, borderLeftWidth: 3 }}>
|
||||
<k.icon className="w-5 h-5" style={{ color: k.color }} />
|
||||
<div>
|
||||
<div className="text-lg font-bold" style={{ color: k.color }}>{k.value}</div>
|
||||
<div className="text-[9px] text-hint">{k.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 보관 구조 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Settings className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-[12px] font-bold text-heading">전체 보관 구조 (4-Tier)</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
{STORAGE_ARCHITECTURE.map(s => (
|
||||
<div key={s.tier} className="bg-surface-overlay rounded-lg p-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<s.icon className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-[11px] font-bold text-heading">{s.tier}</span>
|
||||
</div>
|
||||
<p className="text-[9px] text-hint mb-2">{s.desc}</p>
|
||||
<div className="space-y-1">
|
||||
{[
|
||||
['기술', s.tech],
|
||||
['암호화', s.encryption],
|
||||
['백업', s.backup],
|
||||
].map(([k, v]) => (
|
||||
<div key={k} className="flex justify-between text-[9px] px-2 py-1 bg-surface-raised rounded">
|
||||
<span className="text-muted-foreground">{k}</span>
|
||||
<span className="text-label">{v}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
|
||||
{/* 정책 준수 현황 */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="text-[12px] font-bold text-heading mb-3">정책 준수 점검</div>
|
||||
<div className="space-y-1.5 text-[10px]">
|
||||
{[
|
||||
['유형별 보관기간 기준표', '6/6종 수립 완료', '완료'],
|
||||
['파기 방식 정의', '4/4 방식 적용', '완료'],
|
||||
['파기 승인 절차', '4단계 절차 운영 중', '완료'],
|
||||
['보존 연장 예외 관리', '3건 관리 중 (1건 해제 예정)', '정상'],
|
||||
['백업 데이터 동시 파기', '파기 시 백업 포함 확인', '완료'],
|
||||
['파기 감사 대장', '12건 기록 (금월)', '완료'],
|
||||
['CCTV 30일 보관 준수', '미채택 영상 30일 초과 1건', '주의'],
|
||||
].map(([k, v, s]) => (
|
||||
<div key={k} className="flex items-center gap-2 px-2 py-1.5 bg-surface-overlay rounded">
|
||||
{s === '완료' || s === '정상' ? <CheckCircle className="w-3 h-3 text-green-500" /> : <AlertTriangle className="w-3 h-3 text-yellow-500" />}
|
||||
<span className="text-heading flex-1">{k}</span>
|
||||
<span className="text-hint">{v}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="text-[12px] font-bold text-heading mb-3">최근 파기 이력</div>
|
||||
<div className="space-y-1.5 text-[10px]">
|
||||
{DISPOSAL_AUDIT_LOG.slice(0, 6).map(d => (
|
||||
<div key={d.id} className="flex items-center gap-2 px-2 py-1.5 bg-surface-overlay rounded">
|
||||
<span className="text-muted-foreground w-20">{d.date}</span>
|
||||
<span className="text-heading flex-1 truncate">{d.target}</span>
|
||||
<Badge intent={getStatusIntent(d.result)} size="sm">{d.method}</Badge>
|
||||
<span className="text-hint text-[9px]">{d.volume}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ② 유형별 보관기간 ── */}
|
||||
{tab === 'retention' && (
|
||||
<div className="space-y-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<CalendarClock className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-[12px] font-bold text-heading">데이터 유형별 보관기간 기준표</span>
|
||||
<Badge intent="info" size="xs">{RETENTION_TABLE.length}종 관리</Badge>
|
||||
</div>
|
||||
<table className="w-full text-[10px]">
|
||||
<thead>
|
||||
<tr className="text-hint border-b border-border">
|
||||
<th className="text-left py-2 px-2">데이터 유형</th>
|
||||
<th className="text-left py-2">분류</th>
|
||||
<th className="text-left py-2">법적 근거</th>
|
||||
<th className="text-center py-2">보관기간</th>
|
||||
<th className="text-left py-2">저장 형식</th>
|
||||
<th className="text-center py-2">연간 용량</th>
|
||||
<th className="text-center py-2">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{RETENTION_TABLE.map(r => (
|
||||
<tr key={r.type} className="border-b border-border/50 hover:bg-surface-overlay transition-colors">
|
||||
<td className="py-2.5 px-2 text-heading font-medium">{r.type}</td>
|
||||
<td className="py-2.5"><Badge intent="muted" size="xs">{r.category}</Badge></td>
|
||||
<td className="py-2.5 text-muted-foreground text-[9px]">{r.basis}</td>
|
||||
<td className="py-2.5 text-center text-cyan-400 font-bold">{r.period}</td>
|
||||
<td className="py-2.5 text-muted-foreground text-[9px]">{r.format}</td>
|
||||
<td className="py-2.5 text-center text-muted-foreground">{r.volume}</td>
|
||||
<td className="py-2.5 text-center"><Badge intent={getStatusIntent(r.status)} size="sm">{r.status}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="text-[12px] font-bold text-heading mb-3">관련 법령 요약</div>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[
|
||||
{ law: '해사안전법 시행규칙', article: '제42조', content: 'AIS 장치 기록 보존 의무 (5년)' },
|
||||
{ law: '해양경비법', article: '제18조', content: '단속 기록 작성·보존 의무' },
|
||||
{ law: '공공기록물 관리에 관한 법률', article: '제19조', content: '기록물 보존기간 준수 의무' },
|
||||
{ law: '개인정보보호법', article: '제21조', content: '목적 달성 후 지체 없이 파기' },
|
||||
{ law: '정보통신망법', article: '제45조', content: '접속기록 5년 보관 의무' },
|
||||
{ law: '형사소송법', article: '제198조', content: '수사기록 보존 의무' },
|
||||
].map(l => (
|
||||
<div key={l.law} className="bg-surface-overlay rounded-lg p-3">
|
||||
<div className="text-[11px] font-bold text-heading mb-1">{l.law}</div>
|
||||
<Badge intent="muted" size="xs">{l.article}</Badge>
|
||||
<div className="text-[9px] text-hint mt-1.5">{l.content}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ③ 파기 절차 ── */}
|
||||
{tab === 'disposal' && (
|
||||
<div className="space-y-3">
|
||||
{/* 파기 승인 워크플로우 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Trash2 className="w-4 h-4 text-red-400" />
|
||||
<span className="text-[12px] font-bold text-heading">파기 승인 절차 (4단계)</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
{DISPOSAL_WORKFLOW.map((s, i) => (
|
||||
<div key={s.phase} className="relative">
|
||||
{i < DISPOSAL_WORKFLOW.length - 1 && (
|
||||
<ChevronRight className="absolute -right-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-border z-10" />
|
||||
)}
|
||||
<div className="bg-surface-overlay rounded-lg p-3 h-full">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<s.icon className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-[11px] font-bold text-blue-400">{s.phase}</span>
|
||||
</div>
|
||||
<div className="text-[9px] text-cyan-400 mb-2">{s.responsible}</div>
|
||||
<ul className="space-y-1.5">
|
||||
{s.actions.map(a => (
|
||||
<li key={a} className="flex items-start gap-1.5 text-[9px] text-muted-foreground">
|
||||
<CheckCircle className="w-3 h-3 text-green-500 shrink-0 mt-0.5" />
|
||||
<span>{a}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
|
||||
{/* 파기 방식 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Lock className="w-4 h-4 text-red-400" />
|
||||
<span className="text-[12px] font-bold text-heading">파기 방식 정의</span>
|
||||
</div>
|
||||
<table className="w-full text-[10px]">
|
||||
<thead>
|
||||
<tr className="text-hint border-b border-border">
|
||||
<th className="text-left py-2 px-2">파기 방식</th>
|
||||
<th className="text-left py-2">설명</th>
|
||||
<th className="text-left py-2">적용 대상</th>
|
||||
<th className="text-center py-2">암호화</th>
|
||||
<th className="text-center py-2">복구 가능성</th>
|
||||
<th className="text-center py-2">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{DISPOSAL_METHODS.map(m => (
|
||||
<tr key={m.method} className="border-b border-border/50 hover:bg-surface-overlay transition-colors">
|
||||
<td className="py-2.5 px-2 text-heading font-medium">{m.method}</td>
|
||||
<td className="py-2.5 text-muted-foreground text-[9px]">{m.desc}</td>
|
||||
<td className="py-2.5 text-muted-foreground">{m.target}</td>
|
||||
<td className="py-2.5 text-center"><Badge intent={m.encryption.includes('AES') ? 'success' : 'muted'} size="xs">{m.encryption}</Badge></td>
|
||||
<td className="py-2.5 text-center text-red-400 text-[9px] font-medium">{m.recovery}</td>
|
||||
<td className="py-2.5 text-center"><Badge intent="success" size="sm">{m.status}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ④ 예외·연장 ── */}
|
||||
{tab === 'exception' && (
|
||||
<div className="space-y-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<ShieldCheck className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-[12px] font-bold text-heading">보존 연장 예외 현황</span>
|
||||
<Badge intent="warning" size="xs">{EXCEPTIONS.filter(e => e.status === '연장 중').length}건 연장 중</Badge>
|
||||
</div>
|
||||
<table className="w-full text-[10px]">
|
||||
<thead>
|
||||
<tr className="text-hint border-b border-border">
|
||||
<th className="text-left py-2 px-2">예외 ID</th>
|
||||
<th className="text-left py-2">대상 데이터</th>
|
||||
<th className="text-left py-2">사유</th>
|
||||
<th className="text-center py-2">원래 만료</th>
|
||||
<th className="text-center py-2">연장 기한</th>
|
||||
<th className="text-center py-2">승인자</th>
|
||||
<th className="text-center py-2">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{EXCEPTIONS.map(e => (
|
||||
<tr key={e.id} className="border-b border-border/50 hover:bg-surface-overlay transition-colors">
|
||||
<td className="py-2.5 px-2 text-hint font-mono text-[9px]">{e.id}</td>
|
||||
<td className="py-2.5 text-heading font-medium">{e.dataType}</td>
|
||||
<td className="py-2.5 text-muted-foreground text-[9px]">{e.reason}</td>
|
||||
<td className="py-2.5 text-center text-muted-foreground">{e.originalExpiry}</td>
|
||||
<td className="py-2.5 text-center text-cyan-400 font-medium text-[9px]">{e.extendedTo}</td>
|
||||
<td className="py-2.5 text-center text-muted-foreground">{e.approver}</td>
|
||||
<td className="py-2.5 text-center"><Badge intent={getStatusIntent(e.status)} size="sm">{e.status}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<AlertTriangle className="w-4 h-4 text-yellow-400" />
|
||||
<span className="text-[12px] font-bold text-heading">보존 연장 사유 유형</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{EXCEPTION_RULES.map(r => (
|
||||
<div key={r.rule} className="flex items-center gap-3 px-3 py-2.5 bg-surface-overlay rounded-lg">
|
||||
<ShieldCheck className="w-4 h-4 text-purple-400" />
|
||||
<div className="flex-1">
|
||||
<div className="text-[11px] text-heading font-medium">{r.rule}</div>
|
||||
<div className="text-[9px] text-hint">{r.desc}</div>
|
||||
</div>
|
||||
<Badge intent="muted" size="sm">{r.authority}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ⑤ 파기 감사 대장 ── */}
|
||||
{tab === 'audit' && (
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<FileText className="w-4 h-4 text-green-400" />
|
||||
<span className="text-[12px] font-bold text-heading">파기 감사 대장</span>
|
||||
<Badge intent="info" size="xs">{DISPOSAL_AUDIT_LOG.length}건</Badge>
|
||||
</div>
|
||||
<table className="w-full text-[10px]">
|
||||
<thead>
|
||||
<tr className="text-hint border-b border-border">
|
||||
<th className="text-left py-2 px-2">파기 ID</th>
|
||||
<th className="text-center py-2">일시</th>
|
||||
<th className="text-left py-2">파기 대상</th>
|
||||
<th className="text-center py-2">유형</th>
|
||||
<th className="text-center py-2">방식</th>
|
||||
<th className="text-center py-2">용량</th>
|
||||
<th className="text-center py-2">수행자</th>
|
||||
<th className="text-center py-2">승인자</th>
|
||||
<th className="text-center py-2">결과</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{DISPOSAL_AUDIT_LOG.map(d => (
|
||||
<tr key={d.id} className="border-b border-border/50 hover:bg-surface-overlay transition-colors">
|
||||
<td className="py-2.5 px-2 text-hint font-mono text-[9px]">{d.id}</td>
|
||||
<td className="py-2.5 text-center text-muted-foreground">{d.date}</td>
|
||||
<td className="py-2.5 text-heading font-medium text-[9px]">{d.target}</td>
|
||||
<td className="py-2.5 text-center"><Badge intent="muted" size="xs">{d.type}</Badge></td>
|
||||
<td className="py-2.5 text-center text-muted-foreground">{d.method}</td>
|
||||
<td className="py-2.5 text-center text-cyan-400 font-mono">{d.volume}</td>
|
||||
<td className="py-2.5 text-center text-muted-foreground">{d.operator}</td>
|
||||
<td className="py-2.5 text-center text-muted-foreground">{d.approver}</td>
|
||||
<td className="py-2.5 text-center"><Badge intent="success" size="sm">{d.result}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@ -5,3 +5,4 @@ export { AdminPanel } from './AdminPanel';
|
||||
export { DataHub } from './DataHub';
|
||||
export { AISecurityPage } from './AISecurityPage';
|
||||
export { AIAgentSecurityPage } from './AIAgentSecurityPage';
|
||||
export { DataRetentionPolicy } from './DataRetentionPolicy';
|
||||
|
||||
@ -36,7 +36,8 @@
|
||||
"accessLogs": "Access Logs",
|
||||
"loginHistory": "Login History",
|
||||
"aiSecurity": "AI Security",
|
||||
"aiAgentSecurity": "AI Agent Security"
|
||||
"aiAgentSecurity": "AI Agent Security",
|
||||
"dataRetentionPolicy": "Data Retention"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
|
||||
@ -36,7 +36,8 @@
|
||||
"accessLogs": "접근 이력",
|
||||
"loginHistory": "로그인 이력",
|
||||
"aiSecurity": "AI 보안",
|
||||
"aiAgentSecurity": "AI Agent 보안"
|
||||
"aiAgentSecurity": "AI Agent 보안",
|
||||
"dataRetentionPolicy": "데이터 보관·파기"
|
||||
},
|
||||
"status": {
|
||||
"active": "활성",
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user