kcg-ai-monitoring/frontend/src/features/admin/AdminPanel.tsx
htlee da4dc86e90 refactor(frontend): 인라인 버튼/하드코딩 색상 전수 제거
Phase C-2 (인라인 <button>):
- TabBar/TabButton 공통 컴포넌트 신규 (underline/pill/segmented 3종)
- DataHub: 메인 탭 → TabBar + TabButton 전환, 필터 pill 전환,
  CTA 버튼 (작업 등록/스토리지 관리/새로고침) → Button variant
- PermissionsPanel: 역할 생성/저장 → Button variant, icon 버튼 유지
- Python 일괄 치환: 51개 inline <button>에 type="button" 추가
- 남은 <button> type 누락 0건 (multi-line 포함)

Phase C-3 (하드코딩 색상):
- AdminPanel SERVER_STATUS 뱃지: getStatusIntent() 사용으로 통일
- bg-X-500/20 text-X-400 패턴 0건

Phase C-4 (인라인 style):
- LiveMapView BaseMap minHeight → className="min-h-[400px]"
- 나머지 89건 style={{}}은 모두 dynamic value
  (progress width, toggle left, 데이터 기반 color 등)로 정당함

4개 catalog (eventStatuses/enforcementResults/enforcementActions/
patrolStatuses)에 intent 필드 추가, statusIntent.ts 공통 유틸 신규.
이제 모든 Badge가 쇼케이스 팔레트 자동 적용됨.

빌드 검증:
- tsc , eslint , vite build 
- 남은 위반 지표: Badge className 0, button-type-missing 0, 하드코딩 색상 0
2026-04-08 12:36:07 +09:00

92 lines
4.3 KiB
TypeScript

import { useTranslation } from 'react-i18next';
import { Card, CardContent, CardHeader, CardTitle } 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 { Settings, Server, Shield, Database } from 'lucide-react';
/*
* 시스템 관리 — 서버 상태, 디스크, 보안 설정 등 인프라 관리
*/
const SERVER_STATUS = [
{ name: 'WAS-01 (운영)', cpu: 42, mem: 65, disk: 38, status: '정상' },
{ name: 'WAS-02 (이중화)', cpu: 18, mem: 32, disk: 38, status: '정상' },
{ name: 'DB-01 (PostgreSQL)', cpu: 55, mem: 72, disk: 61, status: '정상' },
{ name: 'DB-02 (TimescaleDB)', cpu: 48, mem: 68, disk: 55, status: '정상' },
{ name: 'AI-Engine-01', cpu: 78, mem: 85, disk: 42, status: '주의' },
{ name: 'File-NAS', cpu: 5, mem: 12, disk: 82, status: '경고' },
];
function UsageBar({ value }: { value: number }) {
const color = value > 80 ? 'bg-red-500' : value > 60 ? 'bg-yellow-500' : 'bg-green-500';
return (
<div className="flex items-center gap-1.5">
<div className="w-16 h-1.5 bg-switch-background/60 rounded-full overflow-hidden">
<div className={`h-full rounded-full ${color}`} style={{ width: `${value}%` }} />
</div>
<span className="text-[9px] text-muted-foreground w-8 text-right">{value}%</span>
</div>
);
}
export function AdminPanel() {
const { t } = useTranslation('admin');
return (
<PageContainer>
<PageHeader
icon={Settings}
title={t('adminPanel.title')}
description={t('adminPanel.desc')}
demo
/>
{/* 서버 상태 */}
<div className="grid grid-cols-3 gap-3">
{SERVER_STATUS.map((s) => (
<Card key={s.name} className="bg-surface-raised border-border">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Server className="w-4 h-4 text-hint" />
<span className="text-[11px] font-bold text-heading">{s.name}</span>
</div>
<Badge intent={getStatusIntent(s.status)} size="xs">{s.status}</Badge>
</div>
<div className="space-y-1.5">
<div className="flex items-center justify-between"><span className="text-[9px] text-hint">CPU</span><UsageBar value={s.cpu} /></div>
<div className="flex items-center justify-between"><span className="text-[9px] text-hint">MEM</span><UsageBar value={s.mem} /></div>
<div className="flex items-center justify-between"><span className="text-[9px] text-hint">DISK</span><UsageBar value={s.disk} /></div>
</div>
</CardContent>
</Card>
))}
</div>
{/* 시스템 정보 */}
<div className="grid grid-cols-2 gap-3">
<Card>
<CardHeader className="px-4 pt-3 pb-2">
<CardTitle className="text-xs text-label flex items-center gap-1.5"><Database className="w-3.5 h-3.5 text-blue-400" /></CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4 space-y-2">
{[['PostgreSQL', 'v15.4 운영중'], ['TimescaleDB', 'v2.12 운영중'], ['Redis 캐시', 'v7.2 운영중'], ['Kafka', 'v3.6 클러스터 3노드']].map(([k, v]) => (
<div key={k} className="flex justify-between text-[11px]"><span className="text-hint">{k}</span><span className="text-label">{v}</span></div>
))}
</CardContent>
</Card>
<Card>
<CardHeader className="px-4 pt-3 pb-2">
<CardTitle className="text-xs text-label flex items-center gap-1.5"><Shield className="w-3.5 h-3.5 text-green-400" /> </CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4 space-y-2">
{[['SSL 인증서', '2027-03-15 만료'], ['방화벽', '정상 동작'], ['IDS/IPS', '실시간 감시중'], ['백업', '금일 03:00 완료']].map(([k, v]) => (
<div key={k} className="flex justify-between text-[11px]"><span className="text-hint">{k}</span><span className="text-label">{v}</span></div>
))}
</CardContent>
</Card>
</div>
</PageContainer>
);
}