import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import type { RequestLog, PageResponse } from '../../types/monitoring'; import type { ServiceInfo } from '../../types/service'; import { searchLogs } from '../../services/monitoringService'; import { getServices } from '../../services/serviceService'; import Badge from '../../components/ui/Badge'; import type { BadgeVariant } from '../../components/ui/Badge'; import Button from '../../components/ui/Button'; const METHOD_CLASS: Record = { GET: 'bg-green-100 text-green-800 dark:bg-green-500/15 dark:text-green-400', POST: 'bg-blue-100 text-blue-800 dark:bg-blue-500/15 dark:text-blue-400', PUT: 'bg-orange-100 text-orange-800 dark:bg-orange-500/15 dark:text-orange-400', DELETE: 'bg-red-100 text-red-800 dark:bg-red-500/15 dark:text-red-400', }; const STATUS_VARIANT: Record = { SUCCESS: 'success', FAIL: 'danger', DENIED: 'warning', EXPIRED: 'warning', INVALID_KEY: 'danger', ERROR: 'danger', FAILED: 'default', }; const REQUEST_STATUSES = ['SUCCESS', 'FAIL', 'DENIED', 'EXPIRED', 'INVALID_KEY', 'ERROR', 'FAILED']; const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE']; const DEFAULT_PAGE_SIZE = 20; const formatDate = (d: Date): string => { return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; }; const getToday = (): string => formatDate(new Date()); const getTodayString = getToday; const formatDateTime = (dateStr: string): string => { const d = new Date(dateStr); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hours = String(d.getHours()).padStart(2, '0'); const minutes = String(d.getMinutes()).padStart(2, '0'); const seconds = String(d.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; const RequestLogsPage = () => { const navigate = useNavigate(); const [startDate, setStartDate] = useState(getTodayString()); const [endDate, setEndDate] = useState(getTodayString()); const [datePreset, setDatePreset] = useState('오늘'); const [serviceId, setServiceId] = useState(''); const [requestStatus, setRequestStatus] = useState(''); const [requestMethod, setRequestMethod] = useState(''); const [services, setServices] = useState([]); const [result, setResult] = useState | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(0); const fetchServices = async () => { try { const res = await getServices(); if (res.success && res.data) { setServices(res.data); } } catch { // 서비스 목록 로딩 실패는 무시 } }; const handleSearch = async (page: number) => { setLoading(true); setError(null); setCurrentPage(page); try { const params: Record = { startDate: startDate || undefined, endDate: endDate || undefined, serviceId: serviceId ? Number(serviceId) : undefined, requestStatus: requestStatus || undefined, requestMethod: requestMethod || undefined, page, size: DEFAULT_PAGE_SIZE, }; const res = await searchLogs(params); if (res.success && res.data) { setResult(res.data); } else { setError(res.message || '로그 조회에 실패했습니다.'); } } catch { setError('로그 조회에 실패했습니다.'); } finally { setLoading(false); } }; const handleReset = () => { setStartDate(getTodayString()); setEndDate(getTodayString()); setDatePreset('오늘'); setServiceId(''); setRequestStatus(''); setRequestMethod(''); setCurrentPage(0); }; useEffect(() => { fetchServices(); handleSearch(0); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleResetAndSearch = () => { handleReset(); // 초기화 후 기본값으로 검색 (setState는 비동기이므로 직접 호출) setLoading(true); setError(null); setCurrentPage(0); const today = getTodayString(); const params: Record = { startDate: today, endDate: today, page: 0, size: DEFAULT_PAGE_SIZE, }; searchLogs(params) .then((res) => { if (res.success && res.data) { setResult(res.data); } else { setError(res.message || '로그 조회에 실패했습니다.'); } }) .catch(() => { setError('로그 조회에 실패했습니다.'); }) .finally(() => { setLoading(false); }); }; const handleRowClick = (logId: number) => { navigate(`/monitoring/request-logs/${logId}`); }; const handlePrev = () => { if (currentPage > 0) { handleSearch(currentPage - 1); } }; const handleNext = () => { if (result && currentPage < result.totalPages - 1) { handleSearch(currentPage + 1); } }; return (

Request Logs

{/* Search Form */}
{([ { label: '오늘', fn: () => { const t = getToday(); setStartDate(t); setEndDate(t); setDatePreset('오늘'); } }, { label: '어제', fn: () => { const d = new Date(); d.setDate(d.getDate() - 1); const y = formatDate(d); setStartDate(y); setEndDate(y); setDatePreset('어제'); } }, { label: '최근 7일', fn: () => { const d = new Date(); d.setDate(d.getDate() - 6); setStartDate(formatDate(d)); setEndDate(getToday()); setDatePreset('최근 7일'); } }, { label: '이번 달', fn: () => { const d = new Date(); setStartDate(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`); setEndDate(getToday()); setDatePreset('이번 달'); } }, { label: '지난 달', fn: () => { const d = new Date(); d.setMonth(d.getMonth() - 1); const s = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`; const e = new Date(d.getFullYear(), d.getMonth() + 1, 0); setStartDate(s); setEndDate(formatDate(e)); setDatePreset('지난 달'); } }, { label: '직접 선택', fn: () => { setDatePreset('직접 선택'); } }, ]).map((btn) => ( ))}
{ setStartDate(e.target.value); setDatePreset('직접 선택'); }} className="flex-1 border border-[var(--color-border-strong)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-[var(--color-primary)] focus:outline-none" /> ~ { setEndDate(e.target.value); setDatePreset('직접 선택'); }} className="flex-1 border border-[var(--color-border-strong)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-[var(--color-primary)] focus:outline-none" />
{error && (
{error}
)} {/* Results Table */}
{loading ? (
로딩 중...
) : ( {result && result.content.length > 0 ? ( result.content.map((log) => ( handleRowClick(log.logId)} className="cursor-pointer hover:bg-[var(--color-bg-base)]" > )) ) : ( )}
시간 서비스 Method URL Status Code 응답시간(ms) 상태 IP
{formatDateTime(log.requestedAt)} {log.serviceName || '-'} {log.requestMethod} {log.requestUrl} {log.responseStatus != null ? log.responseStatus : '-'} {log.responseTime != null ? log.responseTime : '-'} {log.requestStatus} {log.requestIp}
검색 결과가 없습니다
)}
{/* Pagination */} {result && result.totalElements > 0 && (
총 {result.totalElements}건 / {result.page + 1} / {result.totalPages} 페이지
)}
); }; export default RequestLogsPage;