-
{k.value}
+
))}
@@ -143,7 +147,7 @@ export function AIAgentSecurityPage() {
{AGENTS.map(a => (
| {a.name} |
- {a.type} |
+ {a.type} |
{a.tools} |
{a.calls24h.toLocaleString()} |
{a.lastCall} |
@@ -171,9 +175,9 @@ export function AIAgentSecurityPage() {
{t.tool} |
{t.desc} |
{t.agent} |
- {t.permission} |
+ {t.permission} |
{t.mcp} |
- {t.status} |
+ {t.status} |
))}
@@ -210,7 +214,7 @@ export function AIAgentSecurityPage() {
{SENSITIVE_COMMANDS.map(c => (
| {c.command} |
- {c.level} |
+ {c.level} |
{c.approval} |
{c.hitl ? : -} |
{c.status} |
@@ -283,7 +287,7 @@ export function AIAgentSecurityPage() {
{l.chain} |
{l.agent} |
{l.tool} |
- {l.result} |
+ {l.result} |
{l.latency} |
))}
diff --git a/frontend/src/features/admin/AISecurityPage.tsx b/frontend/src/features/admin/AISecurityPage.tsx
index 7a96b44..8e91329 100644
--- a/frontend/src/features/admin/AISecurityPage.tsx
+++ b/frontend/src/features/admin/AISecurityPage.tsx
@@ -1,12 +1,13 @@
import { useState } from 'react';
import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
+import { TabBar, TabButton } from '@shared/components/ui/tabs';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { getStatusIntent } from '@shared/constants/statusIntent';
import {
Shield, Database, Brain, Lock, Eye, Activity,
- AlertTriangle, CheckCircle, XCircle, FileText,
- Server, Layers, Settings, Search, RefreshCw,
+ AlertTriangle, CheckCircle,
+ Server, Search, RefreshCw,
} from 'lucide-react';
/*
@@ -19,13 +20,22 @@ import {
type Tab = 'overview' | 'data' | 'training' | 'io' | 'boundary' | 'vulnerability';
+const TABS: { key: Tab; icon: React.ComponentType<{ className?: string }>; label: string }[] = [
+ { key: 'overview', icon: Activity, label: '보안 현황' },
+ { key: 'data', icon: Database, label: '데이터 수집 보안' },
+ { key: 'training', icon: Brain, label: 'AI 학습 보안' },
+ { key: 'io', icon: Lock, label: '입출력 보안' },
+ { key: 'boundary', icon: Server, label: '경계 보안' },
+ { key: 'vulnerability', icon: Search, label: '취약점 점검' },
+];
+
// ─── 보안 현황 KPI ──────────────────
const SECURITY_KPI = [
- { label: '데이터 수집 보안', value: '정상', score: 95, icon: Database, color: '#10b981' },
- { label: 'AI 학습 보안', value: '정상', score: 92, icon: Brain, color: '#3b82f6' },
- { label: '입출력 필터링', value: '정상', score: 98, icon: Lock, color: '#8b5cf6' },
- { label: '경계 보안', value: '주의', score: 85, icon: Server, color: '#f59e0b' },
- { label: '취약점 점검', value: '정상', score: 90, icon: Search, color: '#06b6d4' },
+ { label: '데이터 수집 보안', value: '정상', score: 95, icon: Database, color: 'text-green-400', bg: 'bg-green-500/10' },
+ { label: 'AI 학습 보안', value: '정상', score: 92, icon: Brain, color: 'text-blue-400', bg: 'bg-blue-500/10' },
+ { label: '입출력 필터링', value: '정상', score: 98, icon: Lock, color: 'text-purple-400', bg: 'bg-purple-500/10' },
+ { label: '경계 보안', value: '주의', score: 85, icon: Server, color: 'text-yellow-400', bg: 'bg-yellow-500/10' },
+ { label: '취약점 점검', value: '정상', score: 90, icon: Search, color: 'text-cyan-400', bg: 'bg-cyan-500/10' },
];
// ─── 데이터 수집 보안 ──────────────────
@@ -108,36 +118,30 @@ export function AISecurityPage() {
/>
{/* 탭 */}
-
- {([
- { key: 'overview' as Tab, icon: Activity, label: '보안 현황' },
- { key: 'data' as Tab, icon: Database, label: '데이터 수집 보안' },
- { key: 'training' as Tab, icon: Brain, label: 'AI 학습 보안' },
- { key: 'io' as Tab, icon: Lock, label: '입출력 보안' },
- { key: 'boundary' as Tab, icon: Server, label: '경계 보안' },
- { key: 'vulnerability' as Tab, icon: Search, label: '취약점 점검' },
- ]).map(t => (
-
+
+ {TABS.map(tt => (
+ }
+ onClick={() => setTab(tt.key)}
+ >
+ {tt.label}
+
))}
-
+
{/* ── ① 보안 현황 ── */}
{tab === 'overview' && (
{SECURITY_KPI.map(k => (
-
-
-
-
- {k.score}
- / 100
-
-
{k.label}
+
+
+
+
{k.score}
+
{k.label}
))}
@@ -322,7 +326,9 @@ export function AISecurityPage() {
{v.target} |
{v.version} |
{v.lastScan} |
-
{v.vulns > 0 ? {v.vulns}건 : 0건} |
+
+ 0 ? 'warning' : 'success'} size="xs">{v.vulns}건
+ |
{v.status} |
))}
diff --git a/frontend/src/features/ai-operations/LGCNSMLOpsPage.tsx b/frontend/src/features/ai-operations/LGCNSMLOpsPage.tsx
index 2f4f515..dee3baf 100644
--- a/frontend/src/features/ai-operations/LGCNSMLOpsPage.tsx
+++ b/frontend/src/features/ai-operations/LGCNSMLOpsPage.tsx
@@ -2,8 +2,11 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent } from '@shared/components/ui/card';
import { Badge } from '@shared/components/ui/badge';
+import { TabBar, TabButton } from '@shared/components/ui/tabs';
import { PageContainer, PageHeader } from '@shared/components/layout';
import { getStatusIntent } from '@shared/constants/statusIntent';
+import { getMlopsJobStatusIntent, getMlopsJobStatusLabel } from '@shared/constants/mlopsJobStatuses';
+import { getModelStatusIntent, getModelStatusLabel } from '@shared/constants/modelDeploymentStatuses';
import {
Brain, Database, GitBranch, Activity, Server, Shield,
Settings, Layers, BarChart3, Code, Play,
@@ -21,6 +24,23 @@ import {
type Tab = 'project' | 'environment' | 'model' | 'job' | 'common' | 'monitoring' | 'repository';
+const TABS: { key: Tab; icon: React.ElementType; label: string }[] = [
+ { key: 'project', icon: Layers, label: '프로젝트 관리' },
+ { key: 'environment', icon: Terminal, label: '분석환경 관리' },
+ { key: 'model', icon: Brain, label: '모델 관리' },
+ { key: 'job', icon: Play, label: 'Job 실행 관리' },
+ { key: 'common', icon: Settings, label: '공통서비스' },
+ { key: 'monitoring', icon: Activity, label: '모니터링' },
+ { key: 'repository', icon: Database, label: 'Repository' },
+];
+
+const JOB_PROGRESS_COLOR: Record
= {
+ running: 'bg-blue-500',
+ done: 'bg-green-500',
+ fail: 'bg-red-500',
+ pending: 'bg-muted',
+};
+
// ─── 프로젝트 관리 ──────────────────
const PROJECTS = [
{ id: 'PRJ-001', name: '불법조업 위험도 예측', owner: '분석팀', status: '활성', models: 5, experiments: 12, updated: '2026-04-10' },
@@ -96,15 +116,23 @@ const GPU_RESOURCES = [
];
const SYSTEM_METRICS = [
- { label: '실행중 Job', value: '3', color: '#3b82f6' },
- { label: '대기중 Job', value: '2', color: '#f59e0b' },
- { label: 'GPU 사용률', value: '65%', color: '#10b981' },
- { label: '배포 모델', value: '2', color: '#8b5cf6' },
- { label: 'API 호출/s', value: '221', color: '#06b6d4' },
+ { label: '실행중 Job', value: '3', icon: Play, color: 'text-blue-400', bg: 'bg-blue-500/10' },
+ { label: '대기중 Job', value: '2', icon: RefreshCw, color: 'text-yellow-400', bg: 'bg-yellow-500/10' },
+ { label: 'GPU 사용률', value: '65%', icon: Activity, color: 'text-green-400', bg: 'bg-green-500/10' },
+ { label: '배포 모델', value: '2', icon: Brain, color: 'text-purple-400', bg: 'bg-purple-500/10' },
+ { label: 'API 호출/s', value: '221', icon: Zap, color: 'text-cyan-400', bg: 'bg-cyan-500/10' },
+];
+
+const PROJECT_STATS = [
+ { label: '활성 프로젝트', value: 3, icon: Layers, color: 'text-blue-400', bg: 'bg-blue-500/10' },
+ { label: '총 모델', value: 11, icon: Brain, color: 'text-purple-400', bg: 'bg-purple-500/10' },
+ { label: '총 실험', value: 29, icon: FlaskConical, color: 'text-green-400', bg: 'bg-green-500/10' },
+ { label: '참여 인원', value: 8, icon: Server, color: 'text-yellow-400', bg: 'bg-yellow-500/10' },
];
export function LGCNSMLOpsPage() {
- const { t } = useTranslation('ai');
+ const { t, i18n } = useTranslation('ai');
+ const lang = i18n.language as 'ko' | 'en';
const [tab, setTab] = useState('project');
return (
@@ -118,22 +146,13 @@ export function LGCNSMLOpsPage() {
/>
{/* 탭 */}
-
- {([
- { key: 'project' as Tab, icon: Layers, label: '프로젝트 관리' },
- { key: 'environment' as Tab, icon: Terminal, label: '분석환경 관리' },
- { key: 'model' as Tab, icon: Brain, label: '모델 관리' },
- { key: 'job' as Tab, icon: Play, label: 'Job 실행 관리' },
- { key: 'common' as Tab, icon: Settings, label: '공통서비스' },
- { key: 'monitoring' as Tab, icon: Activity, label: '모니터링' },
- { key: 'repository' as Tab, icon: Database, label: 'Repository' },
- ]).map(t => (
-
+
+ {TABS.map(tt => (
+ } onClick={() => setTab(tt.key)}>
+ {tt.label}
+
))}
-
+
{/* ── ① 프로젝트 관리 ── */}
{tab === 'project' && (
@@ -159,15 +178,13 @@ export function LGCNSMLOpsPage() {
- {[
- { label: '활성 프로젝트', value: 3, icon: Layers, color: '#3b82f6' },
- { label: '총 모델', value: 11, icon: Brain, color: '#8b5cf6' },
- { label: '총 실험', value: 29, icon: FlaskConical, color: '#10b981' },
- { label: '참여 인원', value: 8, icon: Server, color: '#f59e0b' },
- ].map(k => (
-
-
-
+ {PROJECT_STATS.map(k => (
+
+
+
+
+
{k.value}
+
{k.label}
))}
@@ -201,11 +218,11 @@ export function LGCNSMLOpsPage() {
{w.name}
Steps: {w.steps}
{w.progress}%
-
- {w.status === 'running' ? '실행중' : w.status === 'done' ? '완료' : '실패'}
+
+ {getMlopsJobStatusLabel(w.status, t, lang)}
))}
@@ -228,7 +245,11 @@ export function LGCNSMLOpsPage() {
| {m.name} |
{m.framework} |
- {m.status} |
+
+
+ {getModelStatusLabel(m.status, t, lang)}
+
+ |
{m.accuracy}% |
{m.kpi} |
{m.version} |
@@ -294,14 +315,14 @@ export function LGCNSMLOpsPage() {
|
-
- {j.status === 'running' ? '실행중' : j.status === 'done' ? '완료' : '실패'}
+
+ {getMlopsJobStatusLabel(j.status, t, lang)}
|
{j.elapsed} |
@@ -345,8 +366,12 @@ export function LGCNSMLOpsPage() {
{SYSTEM_METRICS.map(k => (
-
-
+
+
+
+
+
{k.value}
+
{k.label}
))}
diff --git a/frontend/src/shared/constants/aiSecurityStatuses.ts b/frontend/src/shared/constants/aiSecurityStatuses.ts
new file mode 100644
index 0000000..e40654c
--- /dev/null
+++ b/frontend/src/shared/constants/aiSecurityStatuses.ts
@@ -0,0 +1,132 @@
+/**
+ * AI 보안 / Agent 보안 도메인 상태 카탈로그
+ *
+ * 사용처: AISecurityPage, AIAgentSecurityPage
+ */
+
+import type { BadgeIntent } from '@lib/theme/variants';
+
+// ─── 위협 수준 ──────────────
+export type ThreatLevel = 'HIGH' | 'MEDIUM' | 'LOW';
+
+export const THREAT_LEVELS: Record
= {
+ HIGH: {
+ i18nKey: 'threatLevel.HIGH',
+ fallback: { ko: '높음', en: 'High' },
+ intent: 'critical',
+ },
+ MEDIUM: {
+ i18nKey: 'threatLevel.MEDIUM',
+ fallback: { ko: '중간', en: 'Medium' },
+ intent: 'warning',
+ },
+ LOW: {
+ i18nKey: 'threatLevel.LOW',
+ fallback: { ko: '낮음', en: 'Low' },
+ intent: 'info',
+ },
+};
+
+export function getThreatLevelIntent(s: string): BadgeIntent {
+ const normalized = s === '높음' ? 'HIGH' : s === '중간' ? 'MEDIUM' : s === '낮음' ? 'LOW' : s;
+ return THREAT_LEVELS[normalized as ThreatLevel]?.intent ?? 'muted';
+}
+
+export function getThreatLevelLabel(
+ s: string,
+ t: (k: string, opts?: { defaultValue?: string }) => string,
+ lang: 'ko' | 'en' = 'ko',
+): string {
+ const normalized = s === '높음' ? 'HIGH' : s === '중간' ? 'MEDIUM' : s === '낮음' ? 'LOW' : s;
+ const meta = THREAT_LEVELS[normalized as ThreatLevel];
+ if (!meta) return s;
+ return t(meta.i18nKey, { defaultValue: meta.fallback[lang] });
+}
+
+// ─── Agent 권한 유형 ──────────────
+export type AgentPermType = 'ADMIN' | 'WRITE' | 'READ' | 'EXECUTE' | 'DELETE';
+
+export const AGENT_PERM_TYPES: Record = {
+ ADMIN: {
+ i18nKey: 'agentPerm.ADMIN',
+ fallback: { ko: '관리자', en: 'Admin' },
+ intent: 'high',
+ },
+ DELETE: {
+ i18nKey: 'agentPerm.DELETE',
+ fallback: { ko: '삭제', en: 'Delete' },
+ intent: 'critical',
+ },
+ WRITE: {
+ i18nKey: 'agentPerm.WRITE',
+ fallback: { ko: '쓰기', en: 'Write' },
+ intent: 'warning',
+ },
+ READ: {
+ i18nKey: 'agentPerm.READ',
+ fallback: { ko: '읽기', en: 'Read' },
+ intent: 'info',
+ },
+ EXECUTE: {
+ i18nKey: 'agentPerm.EXECUTE',
+ fallback: { ko: '실행', en: 'Execute' },
+ intent: 'purple',
+ },
+};
+
+export function getAgentPermTypeIntent(s: string): BadgeIntent {
+ const map: Record = { '관리자': 'ADMIN', '삭제': 'DELETE', '쓰기': 'WRITE', '조회+쓰기': 'WRITE', '읽기': 'READ', '실행': 'EXECUTE' };
+ const key = map[s] ?? s;
+ return AGENT_PERM_TYPES[key as AgentPermType]?.intent ?? 'muted';
+}
+
+export function getAgentPermTypeLabel(
+ s: string,
+ t: (k: string, opts?: { defaultValue?: string }) => string,
+ lang: 'ko' | 'en' = 'ko',
+): string {
+ const map: Record = { '관리자': 'ADMIN', '삭제': 'DELETE', '쓰기': 'WRITE', '조회+쓰기': 'WRITE', '읽기': 'READ', '실행': 'EXECUTE' };
+ const key = map[s] ?? s;
+ const meta = AGENT_PERM_TYPES[key as AgentPermType];
+ if (!meta) return s;
+ return t(meta.i18nKey, { defaultValue: meta.fallback[lang] });
+}
+
+// ─── Agent 실행 결과 ──────────────
+export type AgentExecResult = 'BLOCKED' | 'CONDITIONAL' | 'ALLOWED';
+
+export const AGENT_EXEC_RESULTS: Record = {
+ BLOCKED: {
+ i18nKey: 'agentExec.BLOCKED',
+ fallback: { ko: '차단', en: 'Blocked' },
+ intent: 'critical',
+ },
+ CONDITIONAL: {
+ i18nKey: 'agentExec.CONDITIONAL',
+ fallback: { ko: '조건부 승인', en: 'Conditional' },
+ intent: 'warning',
+ },
+ ALLOWED: {
+ i18nKey: 'agentExec.ALLOWED',
+ fallback: { ko: '정상', en: 'Allowed' },
+ intent: 'success',
+ },
+};
+
+export function getAgentExecResultIntent(s: string): BadgeIntent {
+ const map: Record = { '차단': 'BLOCKED', '조건부 승인': 'CONDITIONAL', '승인→성공': 'ALLOWED', '정상': 'ALLOWED' };
+ const key = map[s] ?? s;
+ return AGENT_EXEC_RESULTS[key as AgentExecResult]?.intent ?? 'muted';
+}
+
+export function getAgentExecResultLabel(
+ s: string,
+ t: (k: string, opts?: { defaultValue?: string }) => string,
+ lang: 'ko' | 'en' = 'ko',
+): string {
+ const map: Record = { '차단': 'BLOCKED', '조건부 승인': 'CONDITIONAL', '승인→성공': 'ALLOWED', '정상': 'ALLOWED' };
+ const key = map[s] ?? s;
+ const meta = AGENT_EXEC_RESULTS[key as AgentExecResult];
+ if (!meta) return s;
+ return t(meta.i18nKey, { defaultValue: meta.fallback[lang] });
+}
diff --git a/frontend/src/shared/constants/catalogRegistry.ts b/frontend/src/shared/constants/catalogRegistry.ts
index 9b4aabd..f463ad8 100644
--- a/frontend/src/shared/constants/catalogRegistry.ts
+++ b/frontend/src/shared/constants/catalogRegistry.ts
@@ -39,6 +39,8 @@ import {
} from './vesselAnalysisStatuses';
import { CONNECTION_STATUSES } from './connectionStatuses';
import { TRAINING_ZONE_TYPES } from './trainingZoneTypes';
+import { MLOPS_JOB_STATUSES } from './mlopsJobStatuses';
+import { THREAT_LEVELS, AGENT_PERM_TYPES, AGENT_EXEC_RESULTS } from './aiSecurityStatuses';
/**
* 카탈로그 공통 메타 — 쇼케이스 렌더와 UI 일관성을 위한 최소 스키마
@@ -259,6 +261,42 @@ export const CATALOG_REGISTRY: CatalogEntry[] = [
description: 'NAVY / AIRFORCE / ARMY / ADD / KCG',
items: TRAINING_ZONE_TYPES,
},
+ {
+ id: 'mlops-job-status',
+ showcaseId: 'TRK-CAT-mlops-job-status',
+ titleKo: 'MLOps Job 상태',
+ titleEn: 'MLOps Job Status',
+ description: 'running / done / fail / pending',
+ source: 'LGCNS MLOps 파이프라인',
+ items: MLOPS_JOB_STATUSES,
+ },
+ {
+ id: 'threat-level',
+ showcaseId: 'TRK-CAT-threat-level',
+ titleKo: 'AI 위협 수준',
+ titleEn: 'AI Threat Level',
+ description: 'HIGH / MEDIUM / LOW',
+ source: 'AI 보안 (SER-10)',
+ items: THREAT_LEVELS,
+ },
+ {
+ id: 'agent-perm-type',
+ showcaseId: 'TRK-CAT-agent-perm-type',
+ titleKo: 'Agent 권한 유형',
+ titleEn: 'Agent Permission Type',
+ description: 'ADMIN / DELETE / WRITE / READ / EXECUTE',
+ source: 'AI Agent 보안 (SER-11)',
+ items: AGENT_PERM_TYPES,
+ },
+ {
+ id: 'agent-exec-result',
+ showcaseId: 'TRK-CAT-agent-exec-result',
+ titleKo: 'Agent 실행 결과',
+ titleEn: 'Agent Execution Result',
+ description: 'BLOCKED / CONDITIONAL / ALLOWED',
+ source: 'AI Agent 보안 (SER-11)',
+ items: AGENT_EXEC_RESULTS,
+ },
];
/** ID로 특정 카탈로그 조회 */
diff --git a/frontend/src/shared/constants/mlopsJobStatuses.ts b/frontend/src/shared/constants/mlopsJobStatuses.ts
new file mode 100644
index 0000000..bcf44a0
--- /dev/null
+++ b/frontend/src/shared/constants/mlopsJobStatuses.ts
@@ -0,0 +1,46 @@
+/**
+ * MLOps Job / 워크플로우 / 파이프라인 상태 카탈로그
+ *
+ * 사용처: LGCNSMLOpsPage
+ */
+
+import type { BadgeIntent } from '@lib/theme/variants';
+
+export type MlopsJobStatus = 'running' | 'done' | 'fail' | 'pending';
+
+export const MLOPS_JOB_STATUSES: Record = {
+ running: {
+ i18nKey: 'mlopsJob.running',
+ fallback: { ko: '실행중', en: 'Running' },
+ intent: 'info',
+ },
+ done: {
+ i18nKey: 'mlopsJob.done',
+ fallback: { ko: '완료', en: 'Done' },
+ intent: 'success',
+ },
+ fail: {
+ i18nKey: 'mlopsJob.fail',
+ fallback: { ko: '실패', en: 'Failed' },
+ intent: 'critical',
+ },
+ pending: {
+ i18nKey: 'mlopsJob.pending',
+ fallback: { ko: '대기', en: 'Pending' },
+ intent: 'muted',
+ },
+};
+
+export function getMlopsJobStatusIntent(s: string): BadgeIntent {
+ return MLOPS_JOB_STATUSES[s as MlopsJobStatus]?.intent ?? 'muted';
+}
+
+export function getMlopsJobStatusLabel(
+ s: string,
+ t: (k: string, opts?: { defaultValue?: string }) => string,
+ lang: 'ko' | 'en' = 'ko',
+): string {
+ const meta = MLOPS_JOB_STATUSES[s as MlopsJobStatus];
+ if (!meta) return s;
+ return t(meta.i18nKey, { defaultValue: meta.fallback[lang] });
+}
diff --git a/frontend/src/shared/constants/statusIntent.ts b/frontend/src/shared/constants/statusIntent.ts
index 0ec87f6..1324a89 100644
--- a/frontend/src/shared/constants/statusIntent.ts
+++ b/frontend/src/shared/constants/statusIntent.ts
@@ -27,6 +27,7 @@ const STATUS_INTENT_MAP: Record = {
'성공': 'success',
'통과': 'success',
'배포': 'success',
+ '허용': 'success',
active: 'success',
running: 'info',
online: 'success',
@@ -73,6 +74,7 @@ const STATUS_INTENT_MAP: Record = {
'수정': 'warning',
'변경': 'warning',
'점검': 'warning',
+ '중지': 'warning',
warning: 'warning',
WARNING: 'warning',
review: 'warning',
@@ -88,6 +90,7 @@ const STATUS_INTENT_MAP: Record = {
'정지': 'critical',
'거부': 'critical',
'폐기': 'critical',
+ '위험': 'critical',
'만료': 'critical',
critical: 'critical',
CRITICAL: 'critical',
@@ -104,6 +107,7 @@ const STATUS_INTENT_MAP: Record = {
// 높음
'높음': 'high',
'높은': 'high',
+ '관리자': 'high',
high: 'high',
HIGH: 'high',
@@ -111,6 +115,7 @@ const STATUS_INTENT_MAP: Record = {
'분석': 'purple',
'학습': 'purple',
'추론': 'purple',
+ '실행': 'purple',
'배포중': 'purple',
analyzing: 'purple',
training: 'purple',