/** * AdminLogsPage * * SysAdmin-only page for viewing gateway application logs. * Color-coded log levels (ERROR, WARNING, INFO, DEBUG). * Auto-refresh with configurable entry count. */ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { FaSync, FaDownload } from 'react-icons/fa'; import api from '../../api'; import styles from './Admin.module.css'; import logStyles from './AdminLogsPage.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; const LOG_LEVEL_COLORS: Record = { ERROR: 'var(--log-error, #e53e3e)', WARNING: 'var(--log-warning, #d69e2e)', INFO: 'var(--log-info, #3182ce)', DEBUG: 'var(--log-debug, #718096)', }; const AUTO_REFRESH_INTERVAL = 5000; function _parseLogLevel(line: string): string | null { const match = line.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} - (\w+) -/); return match ? match[1] : null; } export const AdminLogsPage: React.FC = () => { const { t } = useLanguage(); const [lines, setLines] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [count, setCount] = useState(200); const [countInput, setCountInput] = useState('200'); const [autoRefresh, setAutoRefresh] = useState(false); const [logDir, setLogDir] = useState(''); const logContainerRef = useRef(null); const autoScrollRef = useRef(true); const _fetchLogs = useCallback(async (entryCount?: number) => { try { setLoading(true); setError(null); const n = entryCount ?? count; const response = await api.get(`/api/admin/logs?count=${n}`); setLines(response.data.lines || []); setLogDir(response.data.logDir || ''); } catch (err: any) { setError(err.response?.data?.detail || t('Fehler beim Laden der Logs')); } finally { setLoading(false); } }, [count, t]); const _handleLoad = () => { const parsed = parseInt(countInput, 10); if (isNaN(parsed) || parsed < 1) return; setCount(parsed); _fetchLogs(parsed); }; const _handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') _handleLoad(); }; const _handleDownload = async () => { try { const response = await api.get(`/api/admin/logs/download?count=${count}`, { responseType: 'blob', }); const blob = new Blob([response.data], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `gateway_log_${new Date().toISOString().replace(/[:.]/g, '-')}.log`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } catch (err: any) { setError(err.response?.data?.detail || t('Fehler beim Download')); } }; useEffect(() => { if (!autoRefresh) return; const interval = setInterval(() => _fetchLogs(), AUTO_REFRESH_INTERVAL); return () => clearInterval(interval); }, [autoRefresh, _fetchLogs]); useEffect(() => { if (autoScrollRef.current && logContainerRef.current) { logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; } }, [lines]); const _handleScroll = () => { if (!logContainerRef.current) return; const { scrollTop, scrollHeight, clientHeight } = logContainerRef.current; autoScrollRef.current = scrollHeight - scrollTop - clientHeight < 40; }; return (

{t('Gateway-Logs')}

{lines.length > 0 ? t('{count} Einträge', { count: lines.length }) : t('Keine Logs geladen')} {logDir && — {logDir}}

setCountInput(e.target.value)} onKeyDown={_handleKeyDown} min={1} max={50000} />
{error && (
! {error}
)}
{lines.length === 0 && !loading && (
📋

{t('Keine Logs geladen')}

{t('Gib die gewünschte Anzahl Einträge ein und klicke auf „Laden“.')}

)} {loading && lines.length === 0 && (
{t('Logs werden geladen')}
)} {lines.map((line, idx) => { const level = _parseLogLevel(line); const color = level ? LOG_LEVEL_COLORS[level] : undefined; return (
{line}
); })}
); }; export default AdminLogsPage;