From 99d72e3622f0f088819b11470567f359b24b3ef0 Mon Sep 17 00:00:00 2001 From: htlee Date: Mon, 13 Apr 2026 11:41:49 +0900 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20LGCNS=203=EA=B0=9C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B3=B5=ED=86=B5=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 커스텀 탭 → TabBar/TabButton 공통 컴포넌트 교체 (3개 파일) - hex 색상 맵 → Tailwind 클래스 토큰 전환, style={{ }} 인라인 제거 - 인라인 Badge intent 삼항 → 카탈로그 함수 교체 (getAgentPermTypeIntent 등) - 신규 카탈로그: mlopsJobStatuses (4종), aiSecurityStatuses (위협3+권한5+결과3) - catalogRegistry에 4건 등록 → design-system.html 쇼케이스 자동 노출 - statusIntent.ts에 '허용', '위험', '관리자', '중지', '실행' 매핑 추가 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../features/admin/AIAgentSecurityPage.tsx | 62 ++++---- .../src/features/admin/AISecurityPage.tsx | 66 +++++---- .../features/ai-operations/LGCNSMLOpsPage.tsx | 103 ++++++++------ .../shared/constants/aiSecurityStatuses.ts | 132 ++++++++++++++++++ .../src/shared/constants/catalogRegistry.ts | 38 +++++ .../src/shared/constants/mlopsJobStatuses.ts | 46 ++++++ frontend/src/shared/constants/statusIntent.ts | 5 + 7 files changed, 354 insertions(+), 98 deletions(-) create mode 100644 frontend/src/shared/constants/aiSecurityStatuses.ts create mode 100644 frontend/src/shared/constants/mlopsJobStatuses.ts diff --git a/frontend/src/features/admin/AIAgentSecurityPage.tsx b/frontend/src/features/admin/AIAgentSecurityPage.tsx index 563eea1..ff9530e 100644 --- a/frontend/src/features/admin/AIAgentSecurityPage.tsx +++ b/frontend/src/features/admin/AIAgentSecurityPage.tsx @@ -1,12 +1,15 @@ 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 { getAgentPermTypeIntent, getThreatLevelIntent, getAgentExecResultIntent } from '@shared/constants/aiSecurityStatuses'; import { - Bot, Shield, Lock, Eye, Activity, AlertTriangle, - CheckCircle, FileText, Settings, Terminal, Users, - Key, Layers, Workflow, Hand, + Bot, Activity, AlertTriangle, + CheckCircle, FileText, + Users, + Key, Layers, Hand, } from 'lucide-react'; /* @@ -19,6 +22,15 @@ import { type Tab = 'overview' | 'whitelist' | 'killswitch' | 'identity' | 'mcp' | 'audit'; +const TABS: { key: Tab; icon: React.ElementType; label: string }[] = [ + { key: 'overview', icon: Activity, label: 'Agent 현황' }, + { key: 'whitelist', icon: CheckCircle, label: '화이트리스트 도구' }, + { key: 'killswitch', icon: AlertTriangle, label: '자동 중단·승인' }, + { key: 'identity', icon: Users, label: 'Agent 신원·권한' }, + { key: 'mcp', icon: Layers, label: 'MCP Tool 권한' }, + { key: 'audit', icon: FileText, label: '감사 로그' }, +]; + // ─── Agent 현황 ────────────────── const AGENTS = [ { name: '위험도 분석 Agent', type: '조회 전용', tools: 4, status: '활성', calls24h: 1240, lastCall: '04-10 09:28' }, @@ -29,11 +41,11 @@ const AGENTS = [ ]; const AGENT_KPI = [ - { label: '활성 Agent', value: '4', color: '#10b981' }, - { label: '등록 Tool', value: '26', color: '#3b82f6' }, - { label: '24h 호출', value: '2,656', color: '#8b5cf6' }, - { label: '차단 건수', value: '3', color: '#ef4444' }, - { label: '승인 대기', value: '0', color: '#f59e0b' }, + { label: '활성 Agent', value: '4', color: 'text-green-400', bg: 'bg-green-500/10' }, + { label: '등록 Tool', value: '26', color: 'text-blue-400', bg: 'bg-blue-500/10' }, + { label: '24h 호출', value: '2,656', color: 'text-purple-400', bg: 'bg-purple-500/10' }, + { label: '차단 건수', value: '3', color: 'text-red-400', bg: 'bg-red-500/10' }, + { label: '승인 대기', value: '0', color: 'text-yellow-400', bg: 'bg-yellow-500/10' }, ]; // ─── 화이트리스트 도구 ────────────────── @@ -105,29 +117,21 @@ export function AIAgentSecurityPage() { /> {/* 탭 */} -
- {([ - { key: 'overview' as Tab, icon: Activity, label: 'Agent 현황' }, - { key: 'whitelist' as Tab, icon: CheckCircle, label: '화이트리스트 도구' }, - { key: 'killswitch' as Tab, icon: AlertTriangle, label: '자동 중단·승인' }, - { key: 'identity' as Tab, icon: Users, label: 'Agent 신원·권한' }, - { key: 'mcp' as Tab, icon: Layers, label: 'MCP Tool 권한' }, - { key: 'audit' as Tab, icon: FileText, label: '감사 로그' }, - ]).map(t => ( - + + {TABS.map(tt => ( + } onClick={() => setTab(tt.key)}> + {tt.label} + ))} -
+ {/* ── ① Agent 현황 ── */} {tab === 'overview' && (
{AGENT_KPI.map(k => ( -
-
{k.value}
+
+
{k.value}
{k.label}
))} @@ -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 => ( -
- -
{k.value}
{k.label}
+ {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.progress}%
- - {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}
+
+
+ +
+ {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',