import { useEffect, useState } from 'react' import { guardiaApi } from '../api/clients' interface DRScenario { id: number; name: string; scenario_type: string rto_minutes: number | null; rto_actual_avg?: number | null last_test_at: string | null; last_test_result: string | null rto_met?: boolean | null } interface DRTest { test_id: number; scenario_id: number; test_type: string status: string; started_at: string } interface DRDashboard { total_scenarios: number; pass_count: number fail_count: number; untested_count: number recent_tests: DRTest[] } interface RtoRpo { scenarios: DRScenario[] } const STATUS_COLOR: Record = { PASS: '#22c55e', FAIL: '#ef4444', PARTIAL: '#f59e0b', RUNNING: '#4f6ef7', UNTESTED: '#94a3b8', } const TYPE_LABEL: Record = { SERVER_FAILURE: '서버 장애', SITE_FAILURE: '사이트 장애', DATA_CORRUPTION: '데이터 손상', } function Badge({ status }: { status: string }) { const color = STATUS_COLOR[status] ?? '#94a3b8' return ( {status} ) } function Card({ title, value, sub, color }: { title: string; value: number; sub?: string; color?: string }) { return (
{title}
{value}
{sub &&
{sub}
}
) } export default function DrConsole() { const [dashboard, setDashboard] = useState(null) const [rtoRpo, setRtoRpo] = useState(null) const [loading, setLoading] = useState(true) const [running, setRunning] = useState(null) const [msg, setMsg] = useState('') const load = () => { setLoading(true) Promise.all([ guardiaApi.get('/api/dr/dashboard'), guardiaApi.get('/api/dr/rto-rpo'), ]).then(([d, r]) => { setDashboard(d.data) setRtoRpo(r.data) }).finally(() => setLoading(false)) } useEffect(() => { load() }, []) const runTest = async (scenarioId: number) => { if (!confirm('복구 테스트를 실행하시겠습니까?')) return setRunning(scenarioId) setMsg('') try { const r = await guardiaApi.post('/api/dr/test', { scenario_id: scenarioId, test_type: 'RECOVERY', }) setMsg(`테스트 ${r.data.status} — RTO 실적: ${r.data.rto_actual_minutes ?? '-'}분`) load() } catch (e: any) { setMsg('테스트 실행 실패: ' + (e.response?.data?.detail ?? e.message)) } finally { setRunning(null) } } if (loading) return
로딩 중...
const scenarios = rtoRpo?.scenarios ?? [] return (
{/* 요약 카드 */}
{msg && (
{msg}
)} {/* 시나리오 테이블 */}
DR 시나리오 목록 (RTO/RPO 현황)
{['시나리오명','유형','RTO 목표','RTO 실적','충족 여부','마지막 테스트','상태','액션'].map(h => ( ))} {scenarios.map(sc => ( ))} {scenarios.length === 0 && ( )}
{h}
{sc.name} {TYPE_LABEL[sc.scenario_type] ?? sc.scenario_type} {sc.rto_minutes ? `${sc.rto_minutes}분` : '-'} {sc.rto_actual_avg != null ? `${sc.rto_actual_avg}분` : '기록 없음'} {sc.rto_met === true && ✔ 충족} {sc.rto_met === false && ✘ 초과} {sc.rto_met == null && -} {sc.last_test_at ? new Date(sc.last_test_at).toLocaleDateString('ko-KR') : '-'}
등록된 DR 시나리오가 없습니다.
{/* 최근 테스트 이력 */}
최근 테스트 이력
{['테스트 ID','유형','상태','시작일시'].map(h => ( ))} {(dashboard?.recent_tests ?? []).map(t => ( ))}
{h}
#{t.test_id} {t.test_type} {new Date(t.started_at).toLocaleString('ko-KR')}
) }