- V025 마이그레이션: admin 그룹 하위 3개 메뉴 등록 - LGCNS MLOps (AI 플랫폼, nav_sort=350) - AI 보안 (감사·보안, nav_sort=1800) - AI Agent 보안 (감사·보안, nav_sort=1900) - 페이지 컴포넌트 3개 신규 생성 - componentRegistry, i18n(ko/en) 반영 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
432 lines
25 KiB
TypeScript
432 lines
25 KiB
TypeScript
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 { PageContainer, PageHeader } from '@shared/components/layout';
|
|
import { getStatusIntent } from '@shared/constants/statusIntent';
|
|
import {
|
|
Brain, Database, GitBranch, Activity, Server, Shield,
|
|
Settings, Layers, BarChart3, Code, Play,
|
|
Zap, FlaskConical, CheckCircle,
|
|
Terminal, RefreshCw, Box,
|
|
} from 'lucide-react';
|
|
|
|
/*
|
|
* LGCNS MLOps 플랫폼 통합 페이지
|
|
*
|
|
* LGCNS DAP(Data AI Platform) 기반 MLOps 파이프라인 관리:
|
|
* ① 프로젝트 관리 ② 분석환경 생성 및 관리 ③ 모델 관리 ④ Job 실행 관리
|
|
* ⑤ 공통서비스 ⑥ 모니터링 ⑦ Repository
|
|
*/
|
|
|
|
type Tab = 'project' | 'environment' | 'model' | 'job' | 'common' | 'monitoring' | 'repository';
|
|
|
|
// ─── 프로젝트 관리 ──────────────────
|
|
const PROJECTS = [
|
|
{ id: 'PRJ-001', name: '불법조업 위험도 예측', owner: '분석팀', status: '활성', models: 5, experiments: 12, updated: '2026-04-10' },
|
|
{ id: 'PRJ-002', name: '경비함정 경로추천', owner: '운항팀', status: '활성', models: 3, experiments: 8, updated: '2026-04-09' },
|
|
{ id: 'PRJ-003', name: '다크베셀 탐지', owner: '분석팀', status: '활성', models: 2, experiments: 6, updated: '2026-04-08' },
|
|
{ id: 'PRJ-004', name: '환적 네트워크 분석', owner: '수사팀', status: '대기', models: 1, experiments: 3, updated: '2026-04-05' },
|
|
];
|
|
|
|
// ─── 분석환경 ──────────────────
|
|
const ENVIRONMENTS = [
|
|
{ name: 'Jupyter Notebook', icon: Code, type: 'IDE', gpu: 'Blackwell x1', status: '실행중', user: '김분석', created: '04-10' },
|
|
{ name: 'RStudio Server', icon: BarChart3, type: 'IDE', gpu: '-', status: '중지', user: '이연구', created: '04-08' },
|
|
{ name: 'VS Code Server', icon: Terminal, type: 'IDE', gpu: 'H200 x1', status: '실행중', user: '박개발', created: '04-09' },
|
|
{ name: 'TensorBoard', icon: Activity, type: '모니터링', gpu: '-', status: '실행중', user: '김분석', created: '04-10' },
|
|
];
|
|
|
|
const WORKFLOWS = [
|
|
{ id: 'WF-012', name: 'AIS 전처리 → LSTM 학습', steps: 5, status: 'running', progress: 60, duration: '2h 15m' },
|
|
{ id: 'WF-011', name: '어구분류 피처엔지니어링', steps: 3, status: 'done', progress: 100, duration: '45m' },
|
|
{ id: 'WF-010', name: 'GNN 환적탐지 학습', steps: 4, status: 'done', progress: 100, duration: '3h 20m' },
|
|
];
|
|
|
|
// ─── 모델 관리 ──────────────────
|
|
const MODELS = [
|
|
{ name: '불법조업 위험도 v2.1', framework: 'PyTorch', status: 'DEPLOYED', accuracy: 93.2, version: 'v2.1.0', kpi: 'F1=92.3%', endpoint: '/v1/infer/risk' },
|
|
{ name: '경비함정 경로추천 v1.5', framework: 'TensorFlow', status: 'DEPLOYED', accuracy: 89.7, version: 'v1.5.2', kpi: 'F1=88.4%', endpoint: '/v1/infer/patrol' },
|
|
{ name: 'Transformer 궤적 v0.9', framework: 'PyTorch', status: 'APPROVED', accuracy: 91.2, version: 'v0.9.0', kpi: 'F1=90.5%', endpoint: '-' },
|
|
{ name: 'GNN 환적탐지 v0.3', framework: 'DGL', status: 'TESTING', accuracy: 82.3, version: 'v0.3.0', kpi: 'F1=80.1%', endpoint: '-' },
|
|
];
|
|
|
|
const PARAMETERS = [
|
|
{ name: 'learning_rate', type: 'float', default: '0.001', range: '1e-5 ~ 0.1' },
|
|
{ name: 'batch_size', type: 'int', default: '64', range: '16 ~ 256' },
|
|
{ name: 'epochs', type: 'int', default: '50', range: '10 ~ 200' },
|
|
{ name: 'dropout', type: 'float', default: '0.2', range: '0.0 ~ 0.5' },
|
|
{ name: 'hidden_dim', type: 'int', default: '256', range: '64 ~ 1024' },
|
|
];
|
|
|
|
// ─── Job 실행 관리 ──────────────────
|
|
const JOBS = [
|
|
{ id: 'JOB-088', name: 'LSTM 위험도 학습', type: '학습', resource: 'Blackwell x2', status: 'running', progress: 72, started: '04-10 08:00', elapsed: '3h 28m' },
|
|
{ id: 'JOB-087', name: 'AIS 피처 추출', type: '전처리', resource: 'CPU 16core', status: 'done', progress: 100, started: '04-10 06:00', elapsed: '1h 45m' },
|
|
{ id: 'JOB-086', name: 'GNN 하이퍼파라미터 탐색', type: 'HPS', resource: 'H200 x2', status: 'running', progress: 45, started: '04-10 07:30', elapsed: '2h 10m' },
|
|
{ id: 'JOB-085', name: '위험도 모델 배포', type: '배포', resource: 'Blackwell x1', status: 'done', progress: 100, started: '04-09 14:00', elapsed: '15m' },
|
|
{ id: 'JOB-084', name: 'SAR 이미지 전처리', type: '전처리', resource: 'CPU 32core', status: 'fail', progress: 34, started: '04-09 10:00', elapsed: '0h 50m' },
|
|
];
|
|
|
|
const PIPELINE_STAGES = [
|
|
{ name: '데이터 수집', status: 'done' },
|
|
{ name: '전처리', status: 'done' },
|
|
{ name: '피처 엔지니어링', status: 'done' },
|
|
{ name: '모델 학습', status: 'running' },
|
|
{ name: '모델 평가', status: 'pending' },
|
|
{ name: '모델 배포', status: 'pending' },
|
|
];
|
|
|
|
// ─── 공통서비스 ──────────────────
|
|
const COMMON_SERVICES = [
|
|
{ name: 'Feature Store', icon: Database, desc: '피처 저장소', status: '정상', version: 'v3.2', detail: '20개 피처 · 2.4TB' },
|
|
{ name: 'Model Registry', icon: GitBranch, desc: '모델 레지스트리', status: '정상', version: 'v2.1', detail: '12개 모델 등록' },
|
|
{ name: 'Data Catalog', icon: Layers, desc: '데이터 카탈로그', status: '정상', version: 'v1.5', detail: '48 테이블 · 1.2M rows' },
|
|
{ name: 'Experiment Tracker', icon: FlaskConical, desc: '실험 추적', status: '정상', version: 'v4.0', detail: '42개 실험 기록' },
|
|
{ name: 'API Gateway', icon: Zap, desc: 'API 게이트웨이', status: '정상', version: 'v3.0', detail: '221 req/s' },
|
|
{ name: 'Security Manager', icon: Shield, desc: '보안 관리', status: '정상', version: 'v2.0', detail: 'RBAC + JWT' },
|
|
];
|
|
|
|
// ─── 모니터링 ──────────────────
|
|
const GPU_RESOURCES = [
|
|
{ name: 'Blackwell #1', usage: 78, mem: '38/48GB', temp: '62°C', job: 'JOB-088' },
|
|
{ name: 'Blackwell #2', usage: 52, mem: '25/48GB', temp: '55°C', job: 'JOB-088' },
|
|
{ name: 'H200 #1', usage: 85, mem: '68/80GB', temp: '71°C', job: 'JOB-086' },
|
|
{ name: 'H200 #2', usage: 45, mem: '36/80GB', temp: '48°C', job: '-' },
|
|
];
|
|
|
|
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' },
|
|
];
|
|
|
|
export function LGCNSMLOpsPage() {
|
|
const { t } = useTranslation('ai');
|
|
const [tab, setTab] = useState<Tab>('project');
|
|
|
|
return (
|
|
<PageContainer>
|
|
<PageHeader
|
|
icon={Box}
|
|
iconColor="text-cyan-400"
|
|
title={t('lgcnsMlops.title')}
|
|
description={t('lgcnsMlops.desc')}
|
|
demo
|
|
/>
|
|
|
|
{/* 탭 */}
|
|
<div className="flex gap-0 border-b border-border">
|
|
{([
|
|
{ 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 => (
|
|
<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 === 'project' && (
|
|
<div className="space-y-3">
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">프로젝트 목록</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-right py-2 px-2">수정일</th>
|
|
</tr></thead>
|
|
<tbody>{PROJECTS.map(p => (
|
|
<tr key={p.id} className="border-b border-border/50 hover:bg-surface-overlay transition-colors">
|
|
<td className="py-2 px-2 text-muted-foreground">{p.id}</td>
|
|
<td className="py-2 text-heading font-medium">{p.name}</td>
|
|
<td className="py-2 text-muted-foreground">{p.owner}</td>
|
|
<td className="py-2 text-center"><Badge intent={getStatusIntent(p.status)} size="sm">{p.status}</Badge></td>
|
|
<td className="py-2 text-center text-heading">{p.models}</td>
|
|
<td className="py-2 text-center text-heading">{p.experiments}</td>
|
|
<td className="py-2 px-2 text-right text-muted-foreground">{p.updated}</td>
|
|
</tr>
|
|
))}</tbody>
|
|
</table>
|
|
</CardContent></Card>
|
|
<div className="grid grid-cols-4 gap-2">
|
|
{[
|
|
{ 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 => (
|
|
<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-xl font-bold" style={{ color: k.color }}>{k.value}</div><div className="text-[9px] text-hint">{k.label}</div></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── ② 분석환경 관리 ── */}
|
|
{tab === 'environment' && (
|
|
<div className="space-y-3">
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">분석환경 목록</div>
|
|
<div className="space-y-2">
|
|
{ENVIRONMENTS.map(e => (
|
|
<div key={e.name} className="flex items-center gap-3 px-3 py-2.5 bg-surface-overlay rounded-lg">
|
|
<e.icon className="w-4 h-4 text-cyan-400" />
|
|
<span className="text-[11px] text-heading font-medium w-36">{e.name}</span>
|
|
<Badge intent="muted" size="sm">{e.type}</Badge>
|
|
<span className="text-[10px] text-muted-foreground w-24">GPU: {e.gpu}</span>
|
|
<span className="text-[10px] text-muted-foreground flex-1">사용자: {e.user}</span>
|
|
<Badge intent={getStatusIntent(e.status)} size="sm">{e.status}</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent></Card>
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">워크플로우</div>
|
|
<div className="space-y-2">
|
|
{WORKFLOWS.map(w => (
|
|
<div key={w.id} className="flex items-center gap-3 px-3 py-2.5 bg-surface-overlay rounded-lg">
|
|
<span className="text-[10px] text-muted-foreground w-16">{w.id}</span>
|
|
<span className="text-[11px] text-heading font-medium flex-1">{w.name}</span>
|
|
<span className="text-[10px] text-muted-foreground">Steps: {w.steps}</span>
|
|
<div className="w-20 h-1.5 bg-switch-background rounded-full overflow-hidden">
|
|
<div className={`h-full rounded-full ${w.status === 'running' ? 'bg-blue-500' : w.status === 'done' ? 'bg-green-500' : 'bg-red-500'}`} style={{ width: `${w.progress}%` }} />
|
|
</div>
|
|
<span className="text-[10px] text-muted-foreground">{w.progress}%</span>
|
|
<Badge intent={getStatusIntent(w.status === 'running' ? '실행중' : w.status === 'done' ? '완료' : '실패')} size="sm">
|
|
{w.status === 'running' ? '실행중' : w.status === 'done' ? '완료' : '실패'}
|
|
</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent></Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── ③ 모델 관리 ── */}
|
|
{tab === 'model' && (
|
|
<div className="space-y-3">
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">등록 모델</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-center py-2">상태</th>
|
|
<th className="text-center py-2">정확도</th><th className="text-center py-2">KPI</th><th className="text-left py-2">버전</th><th className="text-left py-2 px-2">Endpoint</th>
|
|
</tr></thead>
|
|
<tbody>{MODELS.map(m => (
|
|
<tr key={m.name} className="border-b border-border/50 hover:bg-surface-overlay transition-colors">
|
|
<td className="py-2 px-2 text-heading font-medium">{m.name}</td>
|
|
<td className="py-2 text-muted-foreground">{m.framework}</td>
|
|
<td className="py-2 text-center"><Badge intent={getStatusIntent(m.status === 'DEPLOYED' ? '활성' : m.status === 'APPROVED' ? '승인' : m.status === 'TESTING' ? '테스트' : '대기')} size="sm">{m.status}</Badge></td>
|
|
<td className="py-2 text-center text-heading font-bold">{m.accuracy}%</td>
|
|
<td className="py-2 text-center text-green-400">{m.kpi}</td>
|
|
<td className="py-2 text-muted-foreground">{m.version}</td>
|
|
<td className="py-2 px-2 text-muted-foreground font-mono text-[9px]">{m.endpoint}</td>
|
|
</tr>
|
|
))}</tbody>
|
|
</table>
|
|
</CardContent></Card>
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">Parameter 관리</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-center py-2">기본값</th><th className="text-left py-2 px-2">범위</th>
|
|
</tr></thead>
|
|
<tbody>{PARAMETERS.map(p => (
|
|
<tr key={p.name} className="border-b border-border/50">
|
|
<td className="py-1.5 px-2 text-heading font-mono">{p.name}</td>
|
|
<td className="py-1.5 text-muted-foreground">{p.type}</td>
|
|
<td className="py-1.5 text-center text-heading">{p.default}</td>
|
|
<td className="py-1.5 px-2 text-muted-foreground">{p.range}</td>
|
|
</tr>
|
|
))}</tbody>
|
|
</table>
|
|
</CardContent></Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── ④ Job 실행 관리 ── */}
|
|
{tab === 'job' && (
|
|
<div className="space-y-3">
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">Job Pipeline</div>
|
|
<div className="flex items-center gap-1 mb-4 px-2">
|
|
{PIPELINE_STAGES.map((s, i) => (
|
|
<div key={s.name} className="flex items-center gap-1 flex-1">
|
|
<div className={`flex-1 py-2 px-3 rounded-lg text-center text-[10px] font-medium ${
|
|
s.status === 'done' ? 'bg-green-500/15 text-green-400 border border-green-500/30' :
|
|
s.status === 'running' ? 'bg-blue-500/15 text-blue-400 border border-blue-500/30 animate-pulse' :
|
|
'bg-surface-overlay text-hint border border-border'
|
|
}`}>
|
|
{s.status === 'done' && <CheckCircle className="w-3 h-3 inline mr-1" />}
|
|
{s.status === 'running' && <RefreshCw className="w-3 h-3 inline mr-1 animate-spin" />}
|
|
{s.name}
|
|
</div>
|
|
{i < PIPELINE_STAGES.length - 1 && <span className="text-hint text-[10px]">→</span>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent></Card>
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">Job 목록</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">Job명</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-right py-2 px-2">소요시간</th>
|
|
</tr></thead>
|
|
<tbody>{JOBS.map(j => (
|
|
<tr key={j.id} className="border-b border-border/50 hover:bg-surface-overlay transition-colors">
|
|
<td className="py-2 px-2 text-muted-foreground">{j.id}</td>
|
|
<td className="py-2 text-heading font-medium">{j.name}</td>
|
|
<td className="py-2 text-center"><Badge intent="muted" size="sm">{j.type}</Badge></td>
|
|
<td className="py-2 text-muted-foreground text-[9px]">{j.resource}</td>
|
|
<td className="py-2">
|
|
<div className="flex items-center gap-2 justify-center">
|
|
<div className="w-16 h-1.5 bg-switch-background rounded-full overflow-hidden">
|
|
<div className={`h-full rounded-full ${j.status === 'running' ? 'bg-blue-500' : j.status === 'done' ? 'bg-green-500' : 'bg-red-500'}`} style={{ width: `${j.progress}%` }} />
|
|
</div>
|
|
<span className="text-heading">{j.progress}%</span>
|
|
</div>
|
|
</td>
|
|
<td className="py-2 text-center">
|
|
<Badge intent={getStatusIntent(j.status === 'running' ? '실행중' : j.status === 'done' ? '완료' : '실패')} size="sm">
|
|
{j.status === 'running' ? '실행중' : j.status === 'done' ? '완료' : '실패'}
|
|
</Badge>
|
|
</td>
|
|
<td className="py-2 px-2 text-right text-muted-foreground">{j.elapsed}</td>
|
|
</tr>
|
|
))}</tbody>
|
|
</table>
|
|
</CardContent></Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── ⑤ 공통서비스 ── */}
|
|
{tab === 'common' && (
|
|
<div className="grid grid-cols-3 gap-3">
|
|
{COMMON_SERVICES.map(s => (
|
|
<Card key={s.name}><CardContent className="p-4">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div className="p-2 rounded-lg bg-cyan-500/10">
|
|
<s.icon className="w-5 h-5 text-cyan-400" />
|
|
</div>
|
|
<div>
|
|
<div className="text-[11px] font-bold text-heading">{s.name}</div>
|
|
<div className="text-[9px] text-hint">{s.desc}</div>
|
|
</div>
|
|
<Badge intent={getStatusIntent(s.status)} size="sm" className="ml-auto">{s.status}</Badge>
|
|
</div>
|
|
<div className="space-y-1 text-[10px]">
|
|
<div className="flex justify-between px-2 py-1 bg-surface-overlay rounded">
|
|
<span className="text-hint">버전</span><span className="text-label">{s.version}</span>
|
|
</div>
|
|
<div className="flex justify-between px-2 py-1 bg-surface-overlay rounded">
|
|
<span className="text-hint">상세</span><span className="text-label">{s.detail}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent></Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* ── ⑥ 모니터링 ── */}
|
|
{tab === 'monitoring' && (
|
|
<div className="space-y-3">
|
|
<div className="flex gap-2">
|
|
{SYSTEM_METRICS.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 }}>
|
|
<div><div className="text-xl font-bold" style={{ color: k.color }}>{k.value}</div><div className="text-[9px] text-hint">{k.label}</div></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">GPU 리소스 현황</div>
|
|
<div className="space-y-2">
|
|
{GPU_RESOURCES.map(g => (
|
|
<div key={g.name} className="flex items-center gap-3 px-3 py-2 bg-surface-overlay rounded-lg">
|
|
<span className="text-[10px] text-heading font-medium w-24">{g.name}</span>
|
|
<div className="flex-1 h-2 bg-switch-background rounded-full overflow-hidden">
|
|
<div className={`h-full rounded-full ${g.usage > 80 ? 'bg-red-500' : g.usage > 60 ? 'bg-yellow-500' : 'bg-green-500'}`} style={{ width: `${g.usage}%` }} />
|
|
</div>
|
|
<span className="text-[10px] text-heading font-bold w-8">{g.usage}%</span>
|
|
<span className="text-[9px] text-hint">{g.mem}</span>
|
|
<span className="text-[9px] text-hint">{g.temp}</span>
|
|
<span className="text-[9px] text-muted-foreground">{g.job}</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-2">
|
|
{[
|
|
{ name: 'DAP API Gateway', status: 'ok', rps: 221 },
|
|
{ name: 'Model Serving', status: 'ok', rps: 186 },
|
|
{ name: 'Feature Store', status: 'ok', rps: 45 },
|
|
{ name: 'Experiment Tracker', status: 'ok', rps: 32 },
|
|
{ name: 'Job Scheduler', status: 'ok', rps: 15 },
|
|
{ name: 'PostgreSQL', status: 'ok', rps: 890 },
|
|
].map(s => (
|
|
<div key={s.name} className="flex items-center gap-3 px-3 py-2 bg-surface-overlay rounded-lg">
|
|
<div className={`w-2 h-2 rounded-full ${s.status === 'ok' ? 'bg-green-500 shadow-[0_0_4px_#22c55e]' : 'bg-yellow-500 shadow-[0_0_4px_#eab308]'}`} />
|
|
<span className="text-[10px] text-heading font-medium flex-1">{s.name}</span>
|
|
<span className="text-[10px] text-muted-foreground">{s.rps} req/s</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent></Card>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── ⑦ Repository ── */}
|
|
{tab === 'repository' && (
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Card><CardContent className="p-4">
|
|
<div className="text-[12px] font-bold text-heading mb-3">소스코드/글로벌 Repository</div>
|
|
<div className="space-y-1.5 text-[10px]">
|
|
{[
|
|
['kcg-ai-monitoring', 'frontend + backend + prediction 모노레포'],
|
|
['kcg-ml-models', '모델 아카이브 (버전별 weight)'],
|
|
['kcg-data-pipeline', 'ETL 스크립트 + Airflow DAG'],
|
|
['kcg-feature-store', '피처 정의 + 변환 로직'],
|
|
].map(([k, v]) => (
|
|
<div key={k} className="flex justify-between px-2 py-1.5 bg-surface-overlay rounded">
|
|
<span className="text-heading font-mono">{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">데이터/분석 Repository</div>
|
|
<div className="space-y-1.5 text-[10px]">
|
|
{[
|
|
['AIS 원본 데이터', 'SNPDB · 5분 주기 증분 · 1.2TB'],
|
|
['학습 데이터셋', '1,456,200건 · 04-03 갱신'],
|
|
['벡터 DB (Milvus)', '1.2M 문서 · 3.6M 벡터'],
|
|
['모델 Artifact', 'S3 · 24개 버전 · 12.8GB'],
|
|
].map(([k, v]) => (
|
|
<div key={k} className="flex justify-between px-2 py-1.5 bg-surface-overlay rounded">
|
|
<span className="text-heading">{k}</span><span className="text-hint">{v}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent></Card>
|
|
</div>
|
|
)}
|
|
</PageContainer>
|
|
);
|
|
}
|