Merge pull request 'release: 2026-04-16.2 — 성능 모니터링 + DAR-10/11 + DAR-03 어구 비교' (#52) from release/2026-04-16.2-main into main
All checks were successful
Build and Deploy KCG AI Monitoring (Frontend) / build-and-deploy (push) Successful in 15s
All checks were successful
Build and Deploy KCG AI Monitoring (Frontend) / build-and-deploy (push) Successful in 15s
This commit is contained in:
커밋
21b5048a9c
@ -0,0 +1,28 @@
|
||||
-- ============================================================
|
||||
-- V028: 성능 모니터링 (PER-01~06) 메뉴 추가
|
||||
-- 시스템관리 > 감사·보안 서브그룹
|
||||
-- ============================================================
|
||||
|
||||
-- 1. 권한 트리 노드 등록
|
||||
INSERT INTO kcg.auth_perm_tree(rsrc_cd, parent_cd, rsrc_nm, rsrc_level, sort_ord)
|
||||
VALUES ('admin:performance-monitoring', 'admin', '성능 모니터링', 1, 59)
|
||||
ON CONFLICT (rsrc_cd) DO NOTHING;
|
||||
|
||||
-- 2. 메뉴 메타데이터 갱신
|
||||
UPDATE kcg.auth_perm_tree
|
||||
SET url_path = '/admin/performance-monitoring',
|
||||
label_key = 'nav.performanceMonitoring',
|
||||
component_key = 'features/admin/PerformanceMonitoring',
|
||||
nav_group = 'admin',
|
||||
nav_sub_group = '감사·보안',
|
||||
nav_sort = 2110,
|
||||
labels = '{"ko":"성능 모니터링","en":"Performance Monitoring"}'
|
||||
WHERE rsrc_cd = 'admin:performance-monitoring';
|
||||
|
||||
-- 3. ADMIN 역할에 전체 권한 부여
|
||||
INSERT INTO kcg.auth_perm(role_sn, rsrc_cd, oper_cd, grant_yn)
|
||||
SELECT r.role_sn, 'admin:performance-monitoring', 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;
|
||||
@ -4,6 +4,15 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2026-04-16.2]
|
||||
|
||||
### 추가
|
||||
- **성능 모니터링(PER-01~06) 메뉴** — 시스템관리 > 감사·보안에 성능 모니터링 페이지 추가 (PerformanceMonitoring 컴포넌트 + V028 메뉴 마이그레이션)
|
||||
- **데이터 모델 검증(DAR-11) 메뉴** — DataModelVerification 페이지 + V027 메뉴
|
||||
- **데이터 보관·파기 정책(DAR-10) 메뉴** — DataRetentionPolicy 페이지 + V026 메뉴
|
||||
- **DAR-03 5종 어구 구조 비교** — AI 모델관리 어구 탐지 탭에 저층 트롤 / 스토우넷 / 자망 / 통발 / 쌍끌이 이미지 및 설명 추가
|
||||
- **단속 계획 탭 확장** — 단일 함정 순찰 작전 / 다함정 순찰 작전 탭 추가 (EnforcementPlan)
|
||||
|
||||
## [2026-04-16]
|
||||
|
||||
### 추가
|
||||
|
||||
@ -128,6 +128,9 @@ export const COMPONENT_REGISTRY: Record<string, LazyComponent> = {
|
||||
'features/admin/DataModelVerification': lazy(() =>
|
||||
import('@features/admin').then((m) => ({ default: m.DataModelVerification })),
|
||||
),
|
||||
'features/admin/PerformanceMonitoring': lazy(() =>
|
||||
import('@features/admin').then((m) => ({ default: m.PerformanceMonitoring })),
|
||||
),
|
||||
// ── 모선 워크플로우 ──
|
||||
'features/parent-inference/ParentReview': lazy(() =>
|
||||
import('@features/parent-inference/ParentReview').then((m) => ({
|
||||
|
||||
637
frontend/src/features/admin/PerformanceMonitoring.tsx
Normal file
637
frontend/src/features/admin/PerformanceMonitoring.tsx
Normal file
@ -0,0 +1,637 @@
|
||||
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 {
|
||||
Activity, Gauge, Users, Database, Brain, Server,
|
||||
CheckCircle, AlertTriangle, TrendingUp, Clock,
|
||||
Zap, Shield, BarChart3, Cpu, HardDrive, Wifi,
|
||||
} from 'lucide-react';
|
||||
|
||||
/*
|
||||
* 성능 모니터링 (PER-01 ~ PER-06)
|
||||
*
|
||||
* 총 사용자 3,000명 (본청 200명·상황실 100명 24/7 + 지방청·관할서·함정)
|
||||
* 통합게이트웨이(V-PASS·VTS·E-nav) + S&P Global AIS A/B 클래스 전제
|
||||
*
|
||||
* ① 성능 현황 ② 응답성(PER-01) ③ 처리용량(PER-02·03)
|
||||
* ④ AI 모델 성능(PER-04) ⑤ 가용성·확장성(PER-05·06)
|
||||
*/
|
||||
|
||||
type Tab = 'overview' | 'response' | 'capacity' | 'aiModel' | 'availability';
|
||||
|
||||
// ─── 성능 KPI ──────────────────
|
||||
const PERF_KPI = [
|
||||
{ label: '현재 동시접속', value: '342', unit: '명', icon: Users, color: '#3b82f6', status: 'normal' },
|
||||
{ label: '대시보드 p95', value: '1.8', unit: '초', icon: Gauge, color: '#10b981', status: 'good' },
|
||||
{ label: '시스템 가동률', value: '99.87', unit: '%', icon: Shield, color: '#06b6d4', status: 'good' },
|
||||
{ label: 'AI 추론 p95', value: '1.4', unit: '초', icon: Brain, color: '#8b5cf6', status: 'good' },
|
||||
{ label: '배치 SLA 준수', value: '100', unit: '%', icon: CheckCircle, color: '#10b981', status: 'good' },
|
||||
{ label: '이벤트 경보', value: '0', unit: '건', icon: AlertTriangle, color: '#f59e0b', status: 'normal' },
|
||||
];
|
||||
|
||||
// ─── SLO 적용 그룹 ──────────────────
|
||||
const USER_GROUPS = [
|
||||
{ group: '본청 상황실', users: 100, concurrent: '100 (100%)', sla: '≤ 2초 대시보드 / ≤ 1초 지도', priority: 'critical' as const, note: '24/7 상시 접속 · 최상위 SLO' },
|
||||
{ group: '본청 기타', users: 100, concurrent: '50 (50%)', sla: '≤ 3초 대시보드', priority: 'high' as const, note: '주간 업무 시간' },
|
||||
{ group: '지방청(5개)', users: 400, concurrent: '120 (30%)', sla: '≤ 3초 대시보드', priority: 'high' as const, note: '관할해역 상황실' },
|
||||
{ group: '관할서', users: 1500, concurrent: '300 (20%)', sla: '≤ 1.5초 조회', priority: 'info' as const, note: '주간 피크' },
|
||||
{ group: '함정·파출소', users: 800, concurrent: '120 (15%)', sla: '≤ 1초 API', priority: 'info' as const, note: '모바일 Agent · 저대역폭' },
|
||||
];
|
||||
|
||||
// ─── PER-01 응답성 SLO vs 실측 ──────────────────
|
||||
const RESPONSE_SLO = [
|
||||
{ target: '메인 대시보드 초기 로드', slo: '2.0초', p50: '0.9초', p95: '1.8초', p99: '2.4초', status: 'good' as const },
|
||||
{ target: '위험도 지도 격자 조회', slo: '2.0초', p50: '0.7초', p95: '1.6초', p99: '2.1초', status: 'good' as const },
|
||||
{ target: '의심 선박·어구 단순 조회', slo: '1.5초', p50: '0.4초', p95: '1.1초', p99: '1.7초', status: 'good' as const },
|
||||
{ target: '복합 분석·시각화', slo: '5.0초', p50: '2.1초', p95: '4.2초', p99: '5.8초', status: 'warn' as const },
|
||||
{ target: 'AI 추론 API (단건)', slo: '2.0초', p50: '0.6초', p95: '1.4초', p99: '1.9초', status: 'good' as const },
|
||||
{ target: '연계 API (read)', slo: '500ms', p50: '120ms', p95: '380ms', p99: '510ms', status: 'warn' as const },
|
||||
{ target: '함정 모바일 Agent API', slo: '1.0초', p50: '0.3초', p95: '0.8초', p99: '1.2초', status: 'good' as const },
|
||||
{ target: 'AI 탐지 알림 End-to-End', slo: '3.0초', p50: '1.1초', p95: '2.3초', p99: '2.8초', status: 'good' as const },
|
||||
];
|
||||
|
||||
// ─── PER-02 동시접속·TPS ──────────────────
|
||||
const CAPACITY_METRICS = [
|
||||
{ metric: '현재 동시접속', current: 342, target: '600 (정상 피크)', max: '900 (작전 피크)', utilization: 57, intent: 'info' as const },
|
||||
{ metric: '현재 TPS', current: 185, target: '400 TPS', max: '600 TPS', utilization: 46, intent: 'info' as const },
|
||||
{ metric: '활성 세션', current: 287, target: '500', max: '750', utilization: 57, intent: 'info' as const },
|
||||
{ metric: 'WebSocket 연결', current: 142, target: '300', max: '500', utilization: 47, intent: 'info' as const },
|
||||
];
|
||||
|
||||
// ─── PER-03 배치 처리 현황 ──────────────────
|
||||
const BATCH_JOBS = [
|
||||
{ name: 'AIS 국내 정제·적재', schedule: '매 5분', volume: '~5 GB/일', sla: '5분', avg: '2분 18초', lastRun: '성공', status: 'success' as const },
|
||||
{ name: 'S&P 글로벌 AIS 집계·격자', schedule: '00:00 야간', volume: '~500 GB/일 (압축 후)', sla: '3시간', avg: '2시간 12분', lastRun: '성공', status: 'success' as const },
|
||||
{ name: '위성영상 타일링·인덱싱', schedule: '수신 직후', volume: '건당 2~10 GB', sla: '2시간/건', avg: '1시간 24분', lastRun: '성공', status: 'success' as const },
|
||||
{ name: '피처 스토어 갱신', schedule: '매시 정각', volume: '~200 MB', sla: '1시간', avg: '8분 42초', lastRun: '성공', status: 'success' as const },
|
||||
{ name: 'AI 모델 재학습 (주간)', schedule: '일요일 02:00', volume: '학습셋 전체', sla: '8시간', avg: '6시간 18분', lastRun: '성공', status: 'success' as const },
|
||||
{ name: '통계·리포트 집계', schedule: '매시 정각', volume: '~50 MB', sla: '30분', avg: '6분 12초', lastRun: '성공', status: 'success' as const },
|
||||
{ name: '해양기상·환경 수집', schedule: '매시 정각', volume: '~500 MB', sla: '10분', avg: '3분 48초', lastRun: '지연', status: 'warn' as const },
|
||||
];
|
||||
|
||||
// ─── PER-04 AI 모델 성능 ──────────────────
|
||||
const AI_MODELS = [
|
||||
{ model: '불법조업 위험도 예측', accuracy: 92.4, precision: 89.1, recall: 87.6, f1: 88.3, rocAuc: 0.948, target: '≥ 85% 정확도', status: 'good' as const },
|
||||
{ model: '순찰 경로 추천 (단일)', accuracy: 94.1, precision: 91.2, recall: 90.5, f1: 90.8, rocAuc: 0.961, target: '≥ 90% 정확도', status: 'good' as const },
|
||||
{ model: '다함정 협력 경로 최적화', accuracy: 91.8, precision: 88.4, recall: 87.9, f1: 88.1, rocAuc: 0.936, target: '≥ 85% 정확도', status: 'good' as const },
|
||||
{ model: '불법 어선 (Dark Vessel) 탐지', accuracy: 96.2, precision: 94.8, recall: 92.3, f1: 93.5, rocAuc: 0.978, target: '≥ 92% 정확도', status: 'good' as const },
|
||||
{ model: '불법 어망·어구 탐지', accuracy: 88.7, precision: 85.2, recall: 83.6, f1: 84.4, rocAuc: 0.912, target: '≥ 85% 정확도', status: 'warn' as const },
|
||||
{ model: 'AIS 조작 패턴 감지', accuracy: 93.5, precision: 91.7, recall: 89.4, f1: 90.5, rocAuc: 0.954, target: '≥ 90% 정확도', status: 'good' as const },
|
||||
];
|
||||
|
||||
// ─── PER-05 가용성·장애복구 ──────────────────
|
||||
const AVAILABILITY_METRICS = [
|
||||
{ component: '애플리케이션 서버 (K8s)', uptime: '99.98%', rto: '≤ 30초', rpo: '0 (stateless)', lastIncident: '없음', status: 'good' as const },
|
||||
{ component: 'DB (PostgreSQL HA)', uptime: '99.95%', rto: '≤ 60초', rpo: '≤ 5초', lastIncident: '2026-03-28', status: 'good' as const },
|
||||
{ component: 'TimescaleDB (Hot)', uptime: '99.92%', rto: '≤ 120초', rpo: '≤ 15초', lastIncident: '2026-04-02', status: 'good' as const },
|
||||
{ component: '벡터 DB (RAG)', uptime: '99.87%', rto: '≤ 180초', rpo: '≤ 30초', lastIncident: '2026-04-08', status: 'warn' as const },
|
||||
{ component: 'NAS 스토리지', uptime: '99.99%', rto: '≤ 60초', rpo: '0 (이중화)', lastIncident: '없음', status: 'good' as const },
|
||||
{ component: '통합게이트웨이', uptime: '99.89%', rto: '≤ 60초', rpo: '≤ 10초', lastIncident: '2026-04-05', status: 'good' as const },
|
||||
{ component: 'S&P Global AIS API', uptime: '99.41%', rto: 'Fallback 즉시', rpo: '국내 신호 대체', lastIncident: '2026-04-12', status: 'warn' as const },
|
||||
{ component: 'LLM Q&A 서버 (H200)', uptime: '99.76%', rto: '≤ 120초', rpo: '0 (stateless)', lastIncident: '2026-04-09', status: 'good' as const },
|
||||
];
|
||||
|
||||
// ─── PER-06 확장성·자원 사용률 ──────────────────
|
||||
const RESOURCE_USAGE = [
|
||||
{ resource: '워커 노드 CPU', current: 48, threshold: 70, max: 80, scalePolicy: 'HPA 자동 확장', unit: '%' },
|
||||
{ resource: '워커 노드 메모리', current: 52, threshold: 75, max: 85, scalePolicy: 'HPA 자동 확장', unit: '%' },
|
||||
{ resource: 'AI 서버 GPU (RTX pro 6000)', current: 61, threshold: 80, max: 90, scalePolicy: '추론 큐잉', unit: '%' },
|
||||
{ resource: 'LLM 서버 GPU (H200)', current: 44, threshold: 75, max: 85, scalePolicy: '요청 병합·배치', unit: '%' },
|
||||
{ resource: 'DB 연결 풀', current: 128, threshold: 300, max: 400, scalePolicy: 'PgBouncer 풀 확대', unit: '개' },
|
||||
{ resource: 'NAS 사용량', current: 28, threshold: 75, max: 90, scalePolicy: '콜드 티어 이관', unit: '% (100TB)' },
|
||||
{ resource: 'Kafka 컨슈머 Lag', current: 142, threshold: 5000, max: 10000, scalePolicy: '파티션 증설', unit: 'msg' },
|
||||
{ resource: 'Redis 캐시 메모리', current: 38, threshold: 70, max: 85, scalePolicy: 'Eviction + 클러스터 확장', unit: '%' },
|
||||
];
|
||||
|
||||
// ─── 성능 영향 최소화 전략 (S&P 글로벌 대응) ──────────────────
|
||||
const IMPACT_REDUCTION = [
|
||||
{ strategy: '이중 수집 파이프라인 물리 분리', target: '국내 vs 글로벌 격리', effect: '글로벌 장애 → 국내 무영향', per: 'PER-01·05' },
|
||||
{ strategy: '경계 조기 필터링', target: '지리·선박 클래스 필터', effect: '원본 50~80% 감축', per: 'PER-03' },
|
||||
{ strategy: '스트림·백프레셔 (Kafka)', target: 'Lag 임계 초과 시 다운샘플링', effect: '온라인 무영향', per: 'PER-01·03' },
|
||||
{ strategy: '티어드 스토리지 (Hot/Warm/Cold)', target: '1~7일 / 30일 / 이후', effect: '쿼리 비용 최소화', per: 'PER-03·06' },
|
||||
{ strategy: '공간 사전 집계 (H3 격자)', target: 'Materialized View', effect: '대시보드 Redis만 조회', per: 'PER-01' },
|
||||
{ strategy: 'Circuit Breaker (S&P)', target: '실패율 50% 차단', effect: '국내 신호 Fallback', per: 'PER-05' },
|
||||
{ strategy: 'K8s PriorityClass 격리', target: '온라인 vs 배치', effect: '상황실 SLO 절대 보장', per: 'PER-01·03' },
|
||||
{ strategy: 'HPA 자동 확장', target: 'CPU/메모리 70% 임계', effect: '피크 자동 대응', per: 'PER-02·06' },
|
||||
];
|
||||
|
||||
const statusIntent = (s: 'good' | 'warn' | 'critical' | 'success'): 'success' | 'warning' | 'critical' => {
|
||||
if (s === 'good' || s === 'success') return 'success';
|
||||
if (s === 'warn') return 'warning';
|
||||
return 'critical';
|
||||
};
|
||||
|
||||
const barColor = (ratio: number): string => {
|
||||
if (ratio < 0.6) return '#10b981';
|
||||
if (ratio < 0.8) return '#f59e0b';
|
||||
return '#ef4444';
|
||||
};
|
||||
|
||||
export function PerformanceMonitoring() {
|
||||
const [tab, setTab] = useState<Tab>('overview');
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
icon={Activity}
|
||||
iconColor="text-cyan-400"
|
||||
title="성능 모니터링"
|
||||
description="PER-01~06 | 응답성·처리용량·AI 모델·가용성·확장성 실시간 현황"
|
||||
demo
|
||||
/>
|
||||
|
||||
{/* 탭 */}
|
||||
<div className="flex gap-0 border-b border-border">
|
||||
{([
|
||||
{ key: 'overview' as Tab, icon: BarChart3, label: '성능 현황' },
|
||||
{ key: 'response' as Tab, icon: Gauge, label: '응답성 (PER-01)' },
|
||||
{ key: 'capacity' as Tab, icon: Users, label: '처리용량 (PER-02·03)' },
|
||||
{ key: 'aiModel' as Tab, icon: Brain, label: 'AI 모델 (PER-04)' },
|
||||
{ key: 'availability' as Tab, icon: Shield, label: '가용성·확장성 (PER-05·06)' },
|
||||
]).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-cyan-400 border-cyan-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">
|
||||
{/* KPI */}
|
||||
<div className="grid grid-cols-6 gap-2">
|
||||
{PERF_KPI.map(k => (
|
||||
<div key={k.label} className="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}<span className="text-[10px] ml-1 text-hint">{k.unit}</span>
|
||||
</div>
|
||||
<div className="text-[9px] text-hint">{k.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 사용자 그룹별 SLO */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Users className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-[12px] font-bold text-heading">사용자 그룹별 SLO (총 2,900명 + 추정)</span>
|
||||
<Badge intent="info" size="xs">본청 200 · 상황실 100 확정</Badge>
|
||||
</div>
|
||||
<table className="w-full text-[11px]">
|
||||
<thead className="text-hint text-[10px] border-b border-border">
|
||||
<tr>
|
||||
<th className="text-left py-2 px-2 font-medium">그룹</th>
|
||||
<th className="text-right py-2 px-2 font-medium">총 인원</th>
|
||||
<th className="text-right py-2 px-2 font-medium">동시접속 추정</th>
|
||||
<th className="text-left py-2 px-2 font-medium">SLA</th>
|
||||
<th className="text-center py-2 px-2 font-medium">우선순위</th>
|
||||
<th className="text-left py-2 px-2 font-medium">특성</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{USER_GROUPS.map(g => (
|
||||
<tr key={g.group} className="border-b border-border/40 hover:bg-surface-overlay/40">
|
||||
<td className="py-2 px-2 text-label font-medium">{g.group}</td>
|
||||
<td className="py-2 px-2 text-right text-label">{g.users.toLocaleString()}명</td>
|
||||
<td className="py-2 px-2 text-right text-heading font-medium">{g.concurrent}</td>
|
||||
<td className="py-2 px-2 text-label">{g.sla}</td>
|
||||
<td className="py-2 px-2 text-center"><Badge intent={g.priority} size="xs">{g.priority === 'critical' ? '최상' : g.priority === 'high' ? '높음' : '일반'}</Badge></td>
|
||||
<td className="py-2 px-2 text-hint">{g.note}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
|
||||
{/* 성능 영향 최소화 전략 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
<span className="text-[12px] font-bold text-heading">성능 영향 최소화 전략 (글로벌 AIS 대응)</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{IMPACT_REDUCTION.map((s, i) => (
|
||||
<div key={s.strategy} className="flex items-start gap-2 px-3 py-2 bg-surface-overlay rounded-lg">
|
||||
<div className="w-5 h-5 rounded-full bg-amber-500/20 flex items-center justify-center shrink-0 text-[9px] font-bold text-amber-400">{i + 1}</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="text-[11px] text-heading font-medium">{s.strategy}</span>
|
||||
<Badge intent="info" size="xs">{s.per}</Badge>
|
||||
</div>
|
||||
<div className="text-[9px] text-hint mb-0.5">대상: {s.target}</div>
|
||||
<div className="text-[9px] text-green-400">효과: {s.effect}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ② 응답성 (PER-01) ── */}
|
||||
{tab === 'response' && (
|
||||
<div className="space-y-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Gauge className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-[12px] font-bold text-heading">PER-01 서비스 응답성 — SLO vs 실측 (p50/p95/p99)</span>
|
||||
</div>
|
||||
<Badge intent="success" size="sm">TER-03 검증 통과</Badge>
|
||||
</div>
|
||||
<table className="w-full text-[11px]">
|
||||
<thead className="text-hint text-[10px] border-b border-border">
|
||||
<tr>
|
||||
<th className="text-left py-2 px-2 font-medium">대상</th>
|
||||
<th className="text-right py-2 px-2 font-medium">SLO 목표</th>
|
||||
<th className="text-right py-2 px-2 font-medium">p50</th>
|
||||
<th className="text-right py-2 px-2 font-medium">p95</th>
|
||||
<th className="text-right py-2 px-2 font-medium">p99</th>
|
||||
<th className="text-center py-2 px-2 font-medium">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{RESPONSE_SLO.map(r => (
|
||||
<tr key={r.target} className="border-b border-border/40 hover:bg-surface-overlay/40">
|
||||
<td className="py-2 px-2 text-label font-medium">{r.target}</td>
|
||||
<td className="py-2 px-2 text-right text-cyan-400 font-medium">{r.slo}</td>
|
||||
<td className="py-2 px-2 text-right text-hint">{r.p50}</td>
|
||||
<td className="py-2 px-2 text-right text-heading font-medium">{r.p95}</td>
|
||||
<td className="py-2 px-2 text-right text-label">{r.p99}</td>
|
||||
<td className="py-2 px-2 text-center"><Badge intent={statusIntent(r.status)} size="xs">{r.status === 'good' ? '정상' : '주의'}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Shield className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-[12px] font-bold text-heading">상황실 전용 SLO (24/7 100명)</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ item: '메인 대시보드 초기 로드', target: '≤ 2초', current: '1.8초', met: true },
|
||||
{ item: '위험도 지도 실시간 갱신', target: '≤ 1초', current: '0.7초', met: true },
|
||||
{ item: 'AI 탐지 알림 수신 → 표출', target: '≤ 3초 E2E', current: '2.3초', met: true },
|
||||
{ item: '단속 계획·경로 조회', target: '≤ 1.5초', current: '1.1초', met: true },
|
||||
{ item: '장애 시 세션 유지', target: '< 30초 복구', current: '18초', met: true },
|
||||
].map(s => (
|
||||
<div key={s.item} className="flex items-center justify-between px-3 py-2 bg-surface-overlay rounded-lg">
|
||||
<div>
|
||||
<div className="text-[11px] text-heading font-medium">{s.item}</div>
|
||||
<div className="text-[9px] text-hint">목표: {s.target}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] text-green-400 font-bold">{s.current}</span>
|
||||
{s.met ? <CheckCircle className="w-4 h-4 text-green-500" /> : <AlertTriangle className="w-4 h-4 text-amber-500" />}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Clock className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-[12px] font-bold text-heading">측정 방법론</span>
|
||||
</div>
|
||||
<ul className="space-y-2 text-[11px]">
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">샘플링:</strong> <span className="text-label">1초 간격 p50/p95/p99 집계</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">도구:</strong> <span className="text-label">OpenTelemetry + Prometheus + Grafana</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">APM:</strong> <span className="text-label">분산 추적 + Trace ID 요청 단위 관통</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">API 재시도:</strong> <span className="text-label">3회 · Exponential Backoff · 타임아웃 3초</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">경보:</strong> <span className="text-label">SLO 위반 지속 5분 → PagerDuty</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">원인 분석:</strong> <span className="text-label">RED/USE 방법론 + 로그·메트릭·추적 상관 분석</span></div>
|
||||
</li>
|
||||
</ul>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ③ 처리용량 (PER-02·03) ── */}
|
||||
{tab === 'capacity' && (
|
||||
<div className="space-y-3">
|
||||
{/* 동시접속·TPS */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Users className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-[12px] font-bold text-heading">PER-02 동시접속·처리용량 (정상 피크 600 / 작전 피크 900)</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
{CAPACITY_METRICS.map(c => (
|
||||
<div key={c.metric} className="px-3 py-3 bg-surface-overlay rounded-lg">
|
||||
<div className="text-[10px] text-hint mb-1">{c.metric}</div>
|
||||
<div className="text-xl font-bold text-heading mb-1">{c.current.toLocaleString()}</div>
|
||||
<div className="text-[9px] text-label mb-2">목표 {c.target} / 최대 {c.max}</div>
|
||||
<div className="h-1.5 bg-surface-raised rounded-full overflow-hidden">
|
||||
<div className="h-full rounded-full transition-all" style={{ width: `${c.utilization}%`, backgroundColor: barColor(c.utilization / 100) }} />
|
||||
</div>
|
||||
<div className="text-[9px] text-hint mt-1">{c.utilization}% 사용 중</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
|
||||
{/* 배치 작업 현황 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Database className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-[12px] font-bold text-heading">PER-03 배치 · 대용량 처리 현황</span>
|
||||
<Badge intent="success" size="xs">SLA 준수 6/7</Badge>
|
||||
</div>
|
||||
<table className="w-full text-[11px]">
|
||||
<thead className="text-hint text-[10px] border-b border-border">
|
||||
<tr>
|
||||
<th className="text-left py-2 px-2 font-medium">배치 작업</th>
|
||||
<th className="text-left py-2 px-2 font-medium">스케줄</th>
|
||||
<th className="text-right py-2 px-2 font-medium">처리 볼륨</th>
|
||||
<th className="text-right py-2 px-2 font-medium">SLA</th>
|
||||
<th className="text-right py-2 px-2 font-medium">평균 소요</th>
|
||||
<th className="text-center py-2 px-2 font-medium">최근 실행</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{BATCH_JOBS.map(j => (
|
||||
<tr key={j.name} className="border-b border-border/40 hover:bg-surface-overlay/40">
|
||||
<td className="py-2 px-2 text-label font-medium">{j.name}</td>
|
||||
<td className="py-2 px-2 text-hint">{j.schedule}</td>
|
||||
<td className="py-2 px-2 text-right text-label">{j.volume}</td>
|
||||
<td className="py-2 px-2 text-right text-cyan-400">{j.sla}</td>
|
||||
<td className="py-2 px-2 text-right text-heading font-medium">{j.avg}</td>
|
||||
<td className="py-2 px-2 text-center"><Badge intent={statusIntent(j.status)} size="xs">{j.lastRun}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
|
||||
{/* 처리 볼륨 산정 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<HardDrive className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-[12px] font-bold text-heading">데이터 처리 볼륨 (국내 + S&P 글로벌)</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3 text-[11px]">
|
||||
<div className="px-3 py-3 bg-surface-overlay rounded-lg">
|
||||
<div className="text-[10px] text-hint mb-1">일 수집 (원본)</div>
|
||||
<div className="text-xl font-bold text-heading">1.6 ~ 3.2 TB</div>
|
||||
<div className="text-[9px] text-label mt-1">AIS(국내) + S&P A/B + 위성 + 환경</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 bg-surface-overlay rounded-lg">
|
||||
<div className="text-[10px] text-hint mb-1">일 적재 (필터·압축 후)</div>
|
||||
<div className="text-xl font-bold text-heading">330 ~ 900 GB</div>
|
||||
<div className="text-[9px] text-green-400 mt-1">경계 필터링 50~80% 감축</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 bg-surface-overlay rounded-lg">
|
||||
<div className="text-[10px] text-hint mb-1">3년 누적 (티어드)</div>
|
||||
<div className="text-xl font-bold text-heading">~360 TB ~ 1 PB</div>
|
||||
<div className="text-[9px] text-amber-400 mt-1">NAS 100TB → 객체스토리지 이관</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ④ AI 모델 성능 (PER-04) ── */}
|
||||
{tab === 'aiModel' && (
|
||||
<div className="space-y-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Brain className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-[12px] font-bold text-heading">PER-04 AI 모델 성능 지표</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge intent="success" size="xs">6개 모델 운영 중</Badge>
|
||||
<Badge intent="warning" size="xs">어망·어구 모델 개선 필요</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<table className="w-full text-[11px]">
|
||||
<thead className="text-hint text-[10px] border-b border-border">
|
||||
<tr>
|
||||
<th className="text-left py-2 px-2 font-medium">모델</th>
|
||||
<th className="text-right py-2 px-2 font-medium">정확도</th>
|
||||
<th className="text-right py-2 px-2 font-medium">정밀도</th>
|
||||
<th className="text-right py-2 px-2 font-medium">재현율</th>
|
||||
<th className="text-right py-2 px-2 font-medium">F1</th>
|
||||
<th className="text-right py-2 px-2 font-medium">ROC-AUC</th>
|
||||
<th className="text-left py-2 px-2 font-medium">목표</th>
|
||||
<th className="text-center py-2 px-2 font-medium">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{AI_MODELS.map(m => (
|
||||
<tr key={m.model} className="border-b border-border/40 hover:bg-surface-overlay/40">
|
||||
<td className="py-2 px-2 text-label font-medium">{m.model}</td>
|
||||
<td className="py-2 px-2 text-right text-heading font-medium">{m.accuracy}%</td>
|
||||
<td className="py-2 px-2 text-right text-label">{m.precision}%</td>
|
||||
<td className="py-2 px-2 text-right text-label">{m.recall}%</td>
|
||||
<td className="py-2 px-2 text-right text-label">{m.f1}</td>
|
||||
<td className="py-2 px-2 text-right text-cyan-400">{m.rocAuc}</td>
|
||||
<td className="py-2 px-2 text-hint text-[10px]">{m.target}</td>
|
||||
<td className="py-2 px-2 text-center"><Badge intent={statusIntent(m.status)} size="xs">{m.status === 'good' ? '통과' : '주의'}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingUp className="w-4 h-4 text-green-400" />
|
||||
<span className="text-[12px] font-bold text-heading">모델 성능 저하 대응</span>
|
||||
</div>
|
||||
<ul className="space-y-2 text-[11px]">
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">학습/검증/테스트 분할:</strong> <span className="text-label">70/15/15 비율, K-Fold 5</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">드리프트 탐지:</strong> <span className="text-label">입력 분포 KL divergence 주간 모니터링</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">성능 저하 임계:</strong> <span className="text-label">F1 3%p 하락 시 자동 재학습 트리거</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">설명가능성:</strong> <span className="text-label">Feature Importance + SHAP 값 제공</span></div>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" />
|
||||
<div><strong className="text-heading">A/B 테스트:</strong> <span className="text-label">Shadow → Canary 5% → 50% → 100% 단계 배포</span></div>
|
||||
</li>
|
||||
</ul>
|
||||
</CardContent></Card>
|
||||
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Cpu className="w-4 h-4 text-amber-400" />
|
||||
<span className="text-[12px] font-bold text-heading">추론 성능 (GPU 활용)</span>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-[11px] text-label">AI 서버 (RTX pro 6000 Blackwell ×2)</span>
|
||||
<span className="text-[11px] text-heading font-bold">61%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-surface-raised rounded-full overflow-hidden">
|
||||
<div className="h-full bg-amber-500 rounded-full" style={{ width: '61%' }} />
|
||||
</div>
|
||||
<div className="text-[9px] text-hint mt-1">추론 단건 평균 1.4초 · 큐 대기 <200ms</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-[11px] text-label">LLM 서버 (H200 ×2)</span>
|
||||
<span className="text-[11px] text-heading font-bold">44%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-surface-raised rounded-full overflow-hidden">
|
||||
<div className="h-full bg-green-500 rounded-full" style={{ width: '44%' }} />
|
||||
</div>
|
||||
<div className="text-[9px] text-hint mt-1">Q&A 스트리밍 첫 토큰 평균 380ms</div>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-border/40">
|
||||
<div className="text-[10px] text-hint mb-1">동시 추론 요청 처리</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge intent="info" size="xs">최대 100 요청/초</Badge>
|
||||
<Badge intent="success" size="xs">큐잉 지연 <1%</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── ⑤ 가용성·확장성 (PER-05·06) ── */}
|
||||
{tab === 'availability' && (
|
||||
<div className="space-y-3">
|
||||
{/* 가용성 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Shield className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-[12px] font-bold text-heading">PER-05 가용성 및 장애복구 (목표 ≥ 99.9%)</span>
|
||||
</div>
|
||||
<table className="w-full text-[11px]">
|
||||
<thead className="text-hint text-[10px] border-b border-border">
|
||||
<tr>
|
||||
<th className="text-left py-2 px-2 font-medium">구성요소</th>
|
||||
<th className="text-right py-2 px-2 font-medium">가동률</th>
|
||||
<th className="text-right py-2 px-2 font-medium">RTO</th>
|
||||
<th className="text-right py-2 px-2 font-medium">RPO</th>
|
||||
<th className="text-left py-2 px-2 font-medium">최근 장애</th>
|
||||
<th className="text-center py-2 px-2 font-medium">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{AVAILABILITY_METRICS.map(a => (
|
||||
<tr key={a.component} className="border-b border-border/40 hover:bg-surface-overlay/40">
|
||||
<td className="py-2 px-2 text-label font-medium">{a.component}</td>
|
||||
<td className="py-2 px-2 text-right text-heading font-medium">{a.uptime}</td>
|
||||
<td className="py-2 px-2 text-right text-cyan-400">{a.rto}</td>
|
||||
<td className="py-2 px-2 text-right text-label">{a.rpo}</td>
|
||||
<td className="py-2 px-2 text-hint">{a.lastIncident}</td>
|
||||
<td className="py-2 px-2 text-center"><Badge intent={statusIntent(a.status)} size="xs">{a.status === 'good' ? '정상' : '주의'}</Badge></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent></Card>
|
||||
|
||||
{/* 확장성 */}
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Server className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-[12px] font-bold text-heading">PER-06 확장성 및 자원 사용률</span>
|
||||
<Badge intent="info" size="xs">2배(6,000명) 확장 목표</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{RESOURCE_USAGE.map(r => {
|
||||
const ratio = r.current / r.max;
|
||||
return (
|
||||
<div key={r.resource} className="px-3 py-3 bg-surface-overlay rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-[11px] text-label font-medium">{r.resource}</span>
|
||||
<span className="text-[11px] text-heading font-bold">{r.current}{r.unit === '%' || r.unit.startsWith('%') ? '%' : ' ' + r.unit}</span>
|
||||
</div>
|
||||
<div className="h-2 bg-surface-raised rounded-full overflow-hidden mb-2">
|
||||
<div className="h-full rounded-full transition-all" style={{ width: `${(ratio) * 100}%`, backgroundColor: barColor(ratio) }} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-[9px]">
|
||||
<span className="text-hint">경보 {r.threshold}{r.unit === '%' || r.unit.startsWith('%') ? '%' : ''} · 한계 {r.max}{r.unit === '%' || r.unit.startsWith('%') ? '%' : ''}</span>
|
||||
<Badge intent="muted" size="xs">{r.scalePolicy}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
|
||||
{/* 요약 지표 */}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Wifi className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-[11px] font-bold text-heading">연간 가동률 목표</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-cyan-400">99.9%</div>
|
||||
<div className="text-[9px] text-hint mt-1">월간 다운타임 ≤ 43분</div>
|
||||
</CardContent></Card>
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Clock className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-[11px] font-bold text-heading">RTO 평균</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-purple-400">≤ 60초</div>
|
||||
<div className="text-[9px] text-hint mt-1">자동 페일오버 · Self-healing</div>
|
||||
</CardContent></Card>
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="w-4 h-4 text-green-400" />
|
||||
<span className="text-[11px] font-bold text-heading">RPO 평균</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-green-400">≤ 10초</div>
|
||||
<div className="text-[9px] text-hint mt-1">실시간 복제 + 백업 이중화</div>
|
||||
</CardContent></Card>
|
||||
<Card><CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<TrendingUp className="w-4 h-4 text-amber-400" />
|
||||
<span className="text-[11px] font-bold text-heading">Scale-out 여유</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-amber-400">×2</div>
|
||||
<div className="text-[9px] text-hint mt-1">6,000명까지 선형 확장</div>
|
||||
</CardContent></Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@ -7,3 +7,4 @@ export { AISecurityPage } from './AISecurityPage';
|
||||
export { AIAgentSecurityPage } from './AIAgentSecurityPage';
|
||||
export { DataRetentionPolicy } from './DataRetentionPolicy';
|
||||
export { DataModelVerification } from './DataModelVerification';
|
||||
export { PerformanceMonitoring } from './PerformanceMonitoring';
|
||||
|
||||
@ -38,7 +38,8 @@
|
||||
"aiSecurity": "AI Security",
|
||||
"aiAgentSecurity": "AI Agent Security",
|
||||
"dataRetentionPolicy": "Data Retention",
|
||||
"dataModelVerification": "Model Verification"
|
||||
"dataModelVerification": "Model Verification",
|
||||
"performanceMonitoring": "Performance Monitoring"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
|
||||
@ -38,7 +38,8 @@
|
||||
"aiSecurity": "AI 보안",
|
||||
"aiAgentSecurity": "AI Agent 보안",
|
||||
"dataRetentionPolicy": "데이터 보관·파기",
|
||||
"dataModelVerification": "데이터 모델 검증"
|
||||
"dataModelVerification": "데이터 모델 검증",
|
||||
"performanceMonitoring": "성능 모니터링"
|
||||
},
|
||||
"status": {
|
||||
"active": "활성",
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user