kcg-monitoring/frontend/src/components/common/CollectorMonitor.tsx
htlee 4dd1597111 refactor: 인라인 CSS 정리 — 공통 클래스 추출 + Tailwind 전환
- CollectorMonitor: 29건 인라인 → CSS 클래스 (~3건 동적만 잔존)
- 팝업 공통 CSS: .popup-header, .popup-body, .popup-grid, .popup-label 추출
  - AirportLayer, DamagedShipLayer, InfraLayer, SubmarineCableLayer 적용
- LoginPage: var(--kcg-*) 인라인 → Tailwind 유틸리티 전환 (hover 포함)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:23:09 +09:00

143 lines
4.6 KiB
TypeScript
Raw Blame 히스토리

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect, useCallback } from 'react';
import { fetchCollectorStatus } from '../../services/collectorStatus';
import type { CollectorInfo } from '../../services/collectorStatus';
interface CollectorMonitorProps {
onClose: () => void;
}
function formatRelativeTime(isoString: string): string {
if (!isoString) return '-';
const diff = Date.now() - new Date(isoString).getTime();
if (diff < 0) return '방금';
const sec = Math.floor(diff / 1000);
if (sec < 60) return `${sec}초 전`;
const min = Math.floor(sec / 60);
if (min < 60) return `${min}분 전`;
const hr = Math.floor(min / 60);
if (hr < 24) return `${hr}시간 전`;
return `${Math.floor(hr / 24)}일 전`;
}
function getStatusColor(info: CollectorInfo): string {
if (!info.lastSuccess) return '#ef4444';
const elapsed = Date.now() - new Date(info.lastSuccess).getTime();
if (elapsed < 5 * 60_000) return '#22c55e';
if (elapsed < 30 * 60_000) return '#eab308';
return '#ef4444';
}
const CollectorMonitor = ({ onClose }: CollectorMonitorProps) => {
const [collectors, setCollectors] = useState<CollectorInfo[]>([]);
const [serverTime, setServerTime] = useState('');
const [error, setError] = useState('');
const refresh = useCallback(async () => {
try {
const data = await fetchCollectorStatus();
setCollectors(data.collectors);
setServerTime(data.serverTime);
setError('');
} catch (e) {
setError(e instanceof Error ? e.message : '연결 실패');
}
}, []);
useEffect(() => {
refresh();
const interval = setInterval(refresh, 10_000);
return () => clearInterval(interval);
}, [refresh]);
return (
<div className='collector-modal'>
{/* Header */}
<div className='collector-header'>
<h3 className='collector-title'>
</h3>
<div className='collector-header-actions'>
{serverTime && (
<span className='collector-server-time'>
: {new Date(serverTime).toLocaleTimeString('ko-KR')}
</span>
)}
<button
onClick={refresh}
className='collector-refresh-btn'
>
</button>
<button
onClick={onClose}
className='collector-close-btn'
>
×
</button>
</div>
</div>
{error && (
<div className='collector-error'>
{error}
</div>
)}
{/* Table */}
<table className='collector-table'>
<thead>
<tr>
<th className='collector-th'></th>
<th className='collector-th'></th>
<th className='collector-th-right'> </th>
<th className='collector-th-right'>/</th>
<th className='collector-th-right'> </th>
<th className='collector-th'> </th>
<th className='collector-th'></th>
</tr>
</thead>
<tbody>
{collectors.map((c) => (
<tr key={c.name}>
<td className='collector-td'>
<span
className='collector-status-dot'
style={{ backgroundColor: getStatusColor(c) }}
/>
</td>
<td className='collector-td-name'>{c.name}</td>
<td className='collector-td-right'>{c.lastCount}</td>
<td className='collector-td-right'>
<span className='collector-success-count'>{c.totalSuccess}</span>
{' / '}
<span style={{ color: c.totalFailure > 0 ? '#ef4444' : 'inherit' }}>{c.totalFailure}</span>
</td>
<td className='collector-td-right'>{c.totalItems.toLocaleString()}</td>
<td className='collector-td-last-success'>{formatRelativeTime(c.lastSuccess)}</td>
<td
className='collector-td-error'
style={{
color: c.lastError ? '#ef4444' : 'inherit',
opacity: c.lastError ? 1 : 0.4,
}}
title={c.lastError || ''}
>
{c.lastError || '-'}
</td>
</tr>
))}
{collectors.length === 0 && !error && (
<tr>
<td colSpan={7} className='collector-td-empty'>
...
</td>
</tr>
)}
</tbody>
</table>
</div>
);
};
export default CollectorMonitor;