2026-08-17 13:07:30 +02:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { NavLink, Outlet } from 'react-router-dom';
|
|
|
|
|
import { useUIStore } from '@/store/uiStore';
|
|
|
|
|
import { ChevronRight, ChevronDown, ScrollText, FileText, AlertTriangle, ArrowLeft } from 'lucide-react';
|
2026-08-27 01:43:06 +02:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2026-08-17 13:07:30 +02:00
|
|
|
|
|
|
|
|
interface LogNode {
|
|
|
|
|
title: string;
|
|
|
|
|
path?: string;
|
|
|
|
|
icon?: React.ReactNode;
|
|
|
|
|
children?: LogNode[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const LOG_TREE: LogNode[] = [
|
|
|
|
|
{
|
|
|
|
|
title: 'Audit-Log',
|
|
|
|
|
icon: <ScrollText className="w-4 h-4" />,
|
|
|
|
|
children: [
|
|
|
|
|
{ title: 'Übersicht', path: '/logs/overview' },
|
|
|
|
|
{ title: 'Filter', path: '/logs/filter' },
|
|
|
|
|
{ title: 'Export', path: '/logs/export' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'System-Logs',
|
|
|
|
|
icon: <FileText className="w-4 h-4" />,
|
|
|
|
|
children: [
|
|
|
|
|
{ title: 'Container', path: '/logs/container' },
|
|
|
|
|
{ title: 'Worker', path: '/logs/worker' },
|
|
|
|
|
{ title: 'Datenbank', path: '/logs/database' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Fehler',
|
|
|
|
|
icon: <AlertTriangle className="w-4 h-4" />,
|
|
|
|
|
children: [
|
|
|
|
|
{ title: 'API-Fehler', path: '/logs/api-errors' },
|
|
|
|
|
{ title: 'Plugin-Fehler', path: '/logs/plugin-errors' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function TreeItem({ node, depth }: { node: LogNode; depth: number }) {
|
2026-08-27 01:43:06 +02:00
|
|
|
const { t } = useTranslation();
|
2026-08-17 13:07:30 +02:00
|
|
|
const [expanded, setExpanded] = useState(depth < 1);
|
|
|
|
|
const hasChildren = node.children && node.children.length > 0;
|
|
|
|
|
const paddingLeft = depth * 16 + 12;
|
|
|
|
|
|
|
|
|
|
if (hasChildren) {
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setExpanded(!expanded)}
|
|
|
|
|
className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-secondary-700 hover:bg-secondary-100 transition-colors min-h-touch"
|
|
|
|
|
style={{ paddingLeft }}
|
|
|
|
|
aria-expanded={expanded}
|
|
|
|
|
>
|
|
|
|
|
{expanded ? <ChevronDown className="w-3.5 h-3.5 flex-shrink-0" /> : <ChevronRight className="w-3.5 h-3.5 flex-shrink-0" />}
|
|
|
|
|
{node.icon}
|
|
|
|
|
<span>{node.title}</span>
|
|
|
|
|
</button>
|
|
|
|
|
{expanded && (
|
|
|
|
|
<div>
|
|
|
|
|
{node.children!.map((child) => (
|
|
|
|
|
<TreeItem key={child.path || child.title} node={child} depth={depth + 1} />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<NavLink
|
|
|
|
|
to={node.path!}
|
|
|
|
|
className={({ isActive }) =>
|
|
|
|
|
`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors min-h-touch ${
|
|
|
|
|
isActive
|
|
|
|
|
? 'bg-primary-100 text-primary-700 font-medium'
|
|
|
|
|
: 'text-secondary-600 hover:bg-secondary-100 hover:text-secondary-900'
|
|
|
|
|
}`
|
|
|
|
|
}
|
|
|
|
|
style={{ paddingLeft: paddingLeft + 20 }}
|
|
|
|
|
>
|
|
|
|
|
{node.icon}
|
|
|
|
|
<span>{node.title}</span>
|
|
|
|
|
</NavLink>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function LogsPage() {
|
2026-08-27 01:43:06 +02:00
|
|
|
const { t } = useTranslation();
|
2026-08-17 13:07:30 +02:00
|
|
|
const sidebarOpen = useUIStore((state) => state.sidebarOpen);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-full overflow-hidden" data-testid="logs-page">
|
|
|
|
|
<aside className={`${sidebarOpen ? 'w-64 border-r border-secondary-200' : 'w-0'} flex-shrink-0 min-h-[calc(100vh-4rem)] transition-all duration-200 overflow-hidden`}>
|
|
|
|
|
<div className="w-64 flex-shrink-0 p-4">
|
|
|
|
|
<div className="flex items-center gap-2 mb-4">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => window.location.href = '/start'}
|
|
|
|
|
className="p-1.5 rounded-md text-secondary-400 hover:bg-secondary-100 hover:text-secondary-700 flex items-center justify-center focus:outline-none"
|
2026-08-27 01:43:06 +02:00
|
|
|
aria-label={t('logs.zurückzurstartseite')}
|
|
|
|
|
title={t('logs.zurückzurstartseite')}
|
2026-08-17 13:07:30 +02:00
|
|
|
>
|
|
|
|
|
<ArrowLeft className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
<h1 className="text-xl font-bold text-secondary-900">Logs</h1>
|
|
|
|
|
</div>
|
2026-08-27 01:43:06 +02:00
|
|
|
<nav className="space-y-0.5 overflow-y-auto" role="navigation" aria-label={t('logs.logsnavigation')}>
|
2026-08-17 13:07:30 +02:00
|
|
|
{LOG_TREE.map((node) => (
|
|
|
|
|
<TreeItem key={node.title} node={node} depth={0} />
|
|
|
|
|
))}
|
|
|
|
|
</nav>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
<main className="flex-1 p-6 overflow-y-auto" data-testid="logs-content">
|
|
|
|
|
<Outlet />
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|