From 32c071911a9b8d9d667bdbeda42a04b88fd76158 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Wed, 18 Feb 2026 21:39:45 +0100 Subject: [PATCH] log admin --- src/App.tsx | 3 +- src/config/pageRegistry.tsx | 1 + src/pages/admin/AdminLogsPage.module.css | 106 ++++++++++++ src/pages/admin/AdminLogsPage.tsx | 212 +++++++++++++++++++++++ src/pages/admin/index.ts | 3 +- 5 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 src/pages/admin/AdminLogsPage.module.css create mode 100644 src/pages/admin/AdminLogsPage.tsx diff --git a/src/App.tsx b/src/App.tsx index a37f059..53c8b61 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,7 +37,7 @@ import { DashboardPage } from './pages/Dashboard'; import { SettingsPage } from './pages/Settings'; import { GDPRPage } from './pages/GDPR'; import { FeatureViewPage } from './pages/FeatureView'; -import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminAutomationEventsPage } from './pages/admin'; +import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminAutomationEventsPage, AdminLogsPage } from './pages/admin'; import { PlaygroundPage, WorkflowsPage, AutomationsPage } from './pages/workflows'; import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata'; import { BillingDataView, BillingAdmin } from './pages/billing'; @@ -187,6 +187,7 @@ function App() { } /> } /> } /> + } /> diff --git a/src/config/pageRegistry.tsx b/src/config/pageRegistry.tsx index 0ee9aba..a737c49 100644 --- a/src/config/pageRegistry.tsx +++ b/src/config/pageRegistry.tsx @@ -67,6 +67,7 @@ export const PAGE_ICONS: Record = { 'page.admin.billing': , 'page.admin.automationEvents': , 'page.admin.automation-events': , + 'page.admin.logs': , // Feature pages - Trustee 'page.feature.trustee.dashboard': , diff --git a/src/pages/admin/AdminLogsPage.module.css b/src/pages/admin/AdminLogsPage.module.css new file mode 100644 index 0000000..e19bb30 --- /dev/null +++ b/src/pages/admin/AdminLogsPage.module.css @@ -0,0 +1,106 @@ +/** + * AdminLogsPage Styles + * + * Log viewer specific styles: monospace container, color coding, controls. + */ + +.controls { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + flex-shrink: 0; + flex-wrap: wrap; +} + +.loadGroup { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.controlLabel { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); + white-space: nowrap; +} + +.countInput { + width: 100px; + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); + text-align: center; +} + +.countInput:focus { + outline: none; + border-color: var(--primary-color, #f25843); + box-shadow: 0 0 0 3px rgba(242, 88, 67, 0.1); +} + +.refreshGroup { + display: flex; + align-items: center; +} + +.toggleLabel { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); + cursor: pointer; + user-select: none; +} + +.toggleLabel input[type="checkbox"] { + width: 1rem; + height: 1rem; + cursor: pointer; + accent-color: var(--primary-color, #f25843); +} + +.logContainer { + flex: 1; + min-height: 0; + overflow: auto; + background: var(--bg-tertiary, #1e1e1e); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 0.75rem 1rem; + font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', 'Courier New', monospace; + font-size: 0.8125rem; + line-height: 1.5; +} + +:global(.dark-theme) .logContainer { + background: #0d1117; + border-color: #30363d; +} + +:global(:not(.dark-theme)) .logContainer { + background: #fafafa; +} + +.logLine { + white-space: pre-wrap; + word-break: break-all; + padding: 1px 0; + color: var(--text-primary); +} + +.logLine:hover { + background: var(--bg-secondary, rgba(255, 255, 255, 0.04)); +} + +.logDirHint { + font-size: 0.75rem; + color: var(--text-tertiary); + font-family: monospace; +} diff --git a/src/pages/admin/AdminLogsPage.tsx b/src/pages/admin/AdminLogsPage.tsx new file mode 100644 index 0000000..e8034ba --- /dev/null +++ b/src/pages/admin/AdminLogsPage.tsx @@ -0,0 +1,212 @@ +/** + * 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'; + +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 [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 || 'Fehler beim Laden der Logs'); + } finally { + setLoading(false); + } + }, [count]); + + 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 || '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 ( +
+
+
+

Gateway Logs

+

+ {lines.length > 0 + ? `${lines.length} Einträge` + : 'Keine Logs geladen'} + {logDir && — {logDir}} +

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

Keine Logs geladen

+

+ Gib die gewünschte Anzahl Einträge ein und klicke auf "Laden". +

+
+ )} + {loading && lines.length === 0 && ( +
+
+ Logs werden geladen... +
+ )} + {lines.map((line, idx) => { + const level = _parseLogLevel(line); + const color = level ? LOG_LEVEL_COLORS[level] : undefined; + return ( +
+ {line} +
+ ); + })} +
+
+ ); +}; + +export default AdminLogsPage; diff --git a/src/pages/admin/index.ts b/src/pages/admin/index.ts index 8e5bcc1..ae3b0b5 100644 --- a/src/pages/admin/index.ts +++ b/src/pages/admin/index.ts @@ -15,4 +15,5 @@ export { AdminFeatureRolesPage } from './AdminFeatureRolesPage'; export { AdminFeatureInstanceUsersPage } from './AdminFeatureInstanceUsersPage'; export { AdminMandateRolePermissionsPage } from './AdminMandateRolePermissionsPage'; export { AdminUserAccessOverviewPage } from './AdminUserAccessOverviewPage'; -export { AdminAutomationEventsPage } from './AdminAutomationEventsPage'; \ No newline at end of file +export { AdminAutomationEventsPage } from './AdminAutomationEventsPage'; +export { AdminLogsPage } from './AdminLogsPage'; \ No newline at end of file