import React, { useState, useEffect } from 'react'; import { adminApi } from '@/lib/api'; import { AlertTriangle, Filter, RefreshCw, CheckCircle, XCircle, Clock, ChevronDown, ChevronUp, Eye, Trash2, Calendar } from 'lucide-react'; interface ErrorLog { id: number; request_id: string; endpoint: string; method: string; path: string; query_params?: any; request_body?: any; error_type: string; error_message: string; error_detail?: any; status_code: number; stack_trace?: string; user_agent?: string; client_ip?: string; response_time_ms?: number; is_resolved: boolean; resolved_at?: string; resolution_notes?: string; created_at: string; } interface ErrorStats { total_errors: number; resolved_errors: number; unresolved_errors: number; resolution_rate: number; errors_by_type: Record; errors_by_status_code: Record; errors_by_endpoint: Record; average_response_time_ms: number; } interface ErrorLogListResponse { items: ErrorLog[]; total: number; page: number; page_size: number; total_pages: number; } const ErrorLogViewer: React.FC = () => { const [errorLogs, setErrorLogs] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [selectedLog, setSelectedLog] = useState(null); const [showDetails, setShowDetails] = useState(false); // Calculate default date range (last 30 days) const getDefaultDateRange = () => { const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 30); return { start_date: startDate.toISOString().split('T')[0], end_date: endDate.toISOString().split('T')[0] }; }; const [filters, setFilters] = useState({ page: 1, page_size: 50, error_type: '', status_code: '', endpoint: '', is_resolved: '', ...getDefaultDateRange() }); useEffect(() => { fetchErrorLogs(); fetchStats(); }, [filters]); const fetchErrorLogs = async () => { setLoading(true); try { const params: Record = {}; Object.entries(filters).forEach(([key, value]) => { if (value !== '' && value !== null && value !== undefined) { params[key] = value.toString(); } }); const data: ErrorLogListResponse = await adminApi.getErrorLogs(params); setErrorLogs(data.items); } catch (error) { console.error('Error fetching error logs:', error); } setLoading(false); }; const fetchStats = async () => { try { const params: Record = {}; if (filters.start_date) params.start_date = filters.start_date; if (filters.end_date) params.end_date = filters.end_date; const data: ErrorStats = await adminApi.getErrorStats(params); setStats(data); } catch (error) { console.error('Error fetching stats:', error); } }; const markAsResolved = async (logId: number, resolved: boolean, notes?: string) => { try { const updatedLog = await adminApi.resolveError(logId, resolved, notes); await fetchErrorLogs(); await fetchStats(); if (selectedLog && selectedLog.id === logId) { setSelectedLog(updatedLog); } } catch (error) { console.error('Error updating error log:', error); } }; const deleteOldLogs = async (daysOld: number, onlyResolved: boolean) => { try { const result = await adminApi.deleteErrorLogs({ days_old: daysOld.toString(), only_resolved: onlyResolved.toString(), }); await fetchErrorLogs(); await fetchStats(); alert(result.message); } catch (error) { console.error('Error deleting old logs:', error); } }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleString(); }; const getStatusCodeColor = (statusCode: number) => { if (statusCode >= 500) return 'text-red-600 bg-red-100'; if (statusCode >= 400) return 'text-orange-600 bg-orange-100'; return 'text-gray-600 bg-gray-100'; }; const getErrorTypeColor = (errorType: string) => { const colors: Record = { 'VALIDATION_ERROR': 'text-blue-600 bg-blue-100', 'DATA_NOT_FOUND': 'text-yellow-600 bg-yellow-100', 'PARSING_ERROR': 'text-purple-600 bg-purple-100', 'SEC_API_ERROR': 'text-red-600 bg-red-100', 'DATABASE_ERROR': 'text-red-800 bg-red-200', 'INTERNAL_SERVER_ERROR': 'text-red-800 bg-red-200' }; return colors[errorType] || 'text-gray-600 bg-gray-100'; }; return (

에러 로그 관리

{/* Statistics */} {stats && (

Total Errors

{stats.total_errors}

Resolved

{stats.resolved_errors}

Unresolved

{stats.unresolved_errors}

Resolution Rate

{stats.resolution_rate.toFixed(1)}%

)} {/* Filters */}

Filters

setFilters(prev => ({ ...prev, endpoint: e.target.value, page: 1 }))} placeholder="e.g. /financial/data" className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" />
setFilters(prev => ({ ...prev, start_date: e.target.value, page: 1 }))} className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" />
setFilters(prev => ({ ...prev, end_date: e.target.value, page: 1 }))} className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" />
{/* Error Logs Table */}
{loading ? ( ) : errorLogs.length === 0 ? ( ) : ( errorLogs.map((log) => ( )) )}
Time Endpoint Error Type Status Message Resolution Actions
Loading error logs...
No error logs found
{formatDate(log.created_at)}
{log.method}
{log.endpoint}
{log.error_type} {log.status_code} {log.error_message} {log.is_resolved ? ( Resolved ) : ( Open )}
{/* Pagination */}
Showing {errorLogs.length} errors
{filters.page}
{/* Error Details Modal */} {showDetails && selectedLog && (

Error Details

{selectedLog.request_id}

{formatDate(selectedLog.created_at)}

{selectedLog.method} {selectedLog.endpoint}

{selectedLog.status_code}

{selectedLog.error_message}

{selectedLog.error_detail && (
                      {JSON.stringify(selectedLog.error_detail, null, 2)}
                    
)} {selectedLog.request_body && (
                      {JSON.stringify(selectedLog.request_body, null, 2)}
                    
)} {selectedLog.stack_trace && (
                      {selectedLog.stack_trace}
                    
)}
)}
); }; export default ErrorLogViewer;