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'; const METHOD_COLOR: Record = { GET: 'bg-green-100 text-green-800', POST: 'bg-blue-100 text-blue-800', PUT: 'bg-orange-100 text-orange-800', DELETE: 'bg-red-100 text-red-800', }; const STATUS_BADGE: Record = { SUCCESS: 'bg-green-100 text-green-800', DENIED: 'bg-red-100 text-red-800', EXPIRED: 'bg-orange-100 text-orange-800', INVALID_KEY: 'bg-red-100 text-red-800', FAILED: 'bg-gray-100 text-gray-800', }; const REQUEST_STATUSES = ['SUCCESS', 'DENIED', 'EXPIRED', 'INVALID_KEY', 'FAILED']; const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE']; const DEFAULT_PAGE_SIZE = 20; const getTodayString = (): string => { const d = new Date(); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; 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 [serviceId, setServiceId] = useState(''); const [requestStatus, setRequestStatus] = useState(''); const [requestMethod, setRequestMethod] = useState(''); const [requestIp, setRequestIp] = 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, requestIp: requestIp || 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()); setServiceId(''); setRequestStatus(''); setRequestMethod(''); setRequestIp(''); 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 */}
setStartDate(e.target.value)} className="flex-1 border rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none" /> ~ setEndDate(e.target.value)} className="flex-1 border rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none" />
setRequestIp(e.target.value)} placeholder="IP 주소" className="w-full border rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 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-gray-50" > )) ) : ( )}
시간 서비스 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;