diff --git a/frontend/src/components/layout/TopBar.tsx b/frontend/src/components/layout/TopBar.tsx
index 3f3a83b..4d07831 100644
--- a/frontend/src/components/layout/TopBar.tsx
+++ b/frontend/src/components/layout/TopBar.tsx
@@ -62,7 +62,7 @@ export function TopBar() {
@@ -149,37 +149,8 @@ export function TopBar() {
className="w-full text-left px-3 py-2 text-sm hover:bg-secondary-50 min-h-touch focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
role="menuitem"
>
- {t('topbar.profile')}
+ {t('topbar.profile', 'Mein Konto')}
- {canAccess('settings:read') && (
-
{ setUserMenuOpen(false); navigate('/settings'); }}
- className="w-full text-left px-3 py-2 text-sm hover:bg-secondary-50 min-h-touch focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
- role="menuitem"
- >
- {t('topbar.settings')}
-
- )}
- {canAccess('audit:read') && (
-
{ setUserMenuOpen(false); navigate('/audit-log'); }}
- className="w-full text-left px-3 py-2 text-sm hover:bg-secondary-50 min-h-touch focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
- role="menuitem"
- >
- {t('nav.auditLog')}
-
- )}
-
setUserMenuOpen(false)}
- className="w-full text-left px-3 py-2 text-sm hover:bg-secondary-50 min-h-touch focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 flex items-center gap-2"
- role="menuitem"
- >
-
- {t('settings.apiDocs', 'API Dokumentation')}
-
,
+ title: 'Entwicklung',
+ icon:
,
children: [
- { title: 'KI-Assistent', path: '/help/ai-assistant' },
- { title: 'Workflows', path: '/help/workflows' },
- { title: 'Automation', path: '/help/automation' },
+ { title: 'API Dokumentation', path: '/help/api-docs' },
],
},
];
diff --git a/frontend/src/pages/Logs.tsx b/frontend/src/pages/Logs.tsx
new file mode 100644
index 0000000..82c9213
--- /dev/null
+++ b/frontend/src/pages/Logs.tsx
@@ -0,0 +1,119 @@
+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';
+
+interface LogNode {
+ title: string;
+ path?: string;
+ icon?: React.ReactNode;
+ children?: LogNode[];
+}
+
+const LOG_TREE: LogNode[] = [
+ {
+ title: 'Audit-Log',
+ icon:
,
+ children: [
+ { title: 'Übersicht', path: '/logs/overview' },
+ { title: 'Filter', path: '/logs/filter' },
+ { title: 'Export', path: '/logs/export' },
+ ],
+ },
+ {
+ title: 'System-Logs',
+ icon:
,
+ children: [
+ { title: 'Container', path: '/logs/container' },
+ { title: 'Worker', path: '/logs/worker' },
+ { title: 'Datenbank', path: '/logs/database' },
+ ],
+ },
+ {
+ title: 'Fehler',
+ icon:
,
+ children: [
+ { title: 'API-Fehler', path: '/logs/api-errors' },
+ { title: 'Plugin-Fehler', path: '/logs/plugin-errors' },
+ ],
+ },
+];
+
+function TreeItem({ node, depth }: { node: LogNode; depth: number }) {
+ const [expanded, setExpanded] = useState(depth < 1);
+ const hasChildren = node.children && node.children.length > 0;
+ const paddingLeft = depth * 16 + 12;
+
+ if (hasChildren) {
+ return (
+
+
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 ? : }
+ {node.icon}
+ {node.title}
+
+ {expanded && (
+
+ {node.children!.map((child) => (
+
+ ))}
+
+ )}
+
+ );
+ }
+
+ return (
+
+ `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}
+ {node.title}
+
+ );
+}
+
+export function LogsPage() {
+ const sidebarOpen = useUIStore((state) => state.sidebarOpen);
+
+ return (
+
+
+
+
+
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"
+ aria-label="Zurück zur Startseite"
+ title="Zurück zur Startseite"
+ >
+
+
+
Logs
+
+
+ {LOG_TREE.map((node) => (
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/StartPage.tsx b/frontend/src/pages/StartPage.tsx
index 8b7fe27..218c517 100644
--- a/frontend/src/pages/StartPage.tsx
+++ b/frontend/src/pages/StartPage.tsx
@@ -8,7 +8,7 @@ import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/store/authStore';
import { useUIStore } from '@/store/uiStore';
-import { Settings, HelpCircle, LayoutGrid, ArrowRight, Plus, Bot, Zap, ArrowLeft } from 'lucide-react';
+import { Settings, HelpCircle, LayoutGrid, ArrowRight, Plus, Bot, Zap, ArrowLeft, ScrollText } from 'lucide-react';
// Workspace tile definition
interface WorkspaceTile {
@@ -42,6 +42,7 @@ export function StartPage() {
{ id: 'settings', label: 'Einstellungen', icon:
, onClick: () => navigate('/settings') },
{ id: 'agents', label: 'Agenten', icon:
, onClick: () => navigate('/agents/overview') },
{ id: 'automation', label: 'Automation', icon:
, onClick: () => navigate('/automation/overview') },
+ { id: 'logs', label: 'Logs', icon:
, onClick: () => navigate('/logs/overview') },
{ id: 'help', label: 'Hilfe', icon:
, onClick: () => navigate('/help/welcome') },
];
diff --git a/frontend/src/pages/help/HelpApiDocs.tsx b/frontend/src/pages/help/HelpApiDocs.tsx
new file mode 100644
index 0000000..671f15e
--- /dev/null
+++ b/frontend/src/pages/help/HelpApiDocs.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+
+export function HelpApiDocsPage() {
+ return (
+
+
API Dokumentation
+
+ Die vollständige API-Dokumentation finden Sie unter /docs (Swagger UI).
+
+
Übersicht
+
+ LeoCRM bietet eine REST-API mit über 224 Endpoints. Die API verwendet JSON für Request- und Response-Bodies.
+
+
Authentifizierung
+
+ Die API verwendet Session-basierte Authentifizierung mit HttpOnly-Cookies. Nach dem Login über POST /api/v1/auth/login wird ein Session-Cookie gesetzt.
+
+
Wichtige Endpoints
+
+ GET /api/v1/contacts — Kontakte abrufen
+ POST /api/v1/contacts — Kontakt erstellen
+ GET /api/v1/calendar/entries — Kalendereinträge
+ GET /api/v1/mail/accounts — Mail-Konten
+ GET /api/v1/plugins/active-manifests — Plugin-Manifeste
+
+
+
+ );
+}
diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx
index 1f533fa..d7d25ca 100644
--- a/frontend/src/routes/index.tsx
+++ b/frontend/src/routes/index.tsx
@@ -83,6 +83,10 @@ const AgentsPlaceholderPage = React.lazy(() => import('@/pages/agents/AgentsPlac
const AutomationPage = React.lazy(() => import('@/pages/Automation').then(m => ({ default: m.AutomationPage })));
const AutomationOverviewPage = React.lazy(() => import('@/pages/automation/AutomationOverview').then(m => ({ default: m.AutomationOverviewPage })));
const AutomationPlaceholderPage = React.lazy(() => import('@/pages/automation/AutomationPlaceholder').then(m => ({ default: m.AutomationPlaceholderPage })));
+const LogsPage = React.lazy(() => import('@/pages/Logs').then(m => ({ default: m.LogsPage })));
+const LogsOverviewPage = React.lazy(() => import('@/pages/logs/LogsOverview').then(m => ({ default: m.LogsOverviewPage })));
+const LogsPlaceholderPage = React.lazy(() => import('@/pages/logs/LogsPlaceholder').then(m => ({ default: m.LogsPlaceholderPage })));
+const HelpApiDocsPage = React.lazy(() => import('@/pages/help/HelpApiDocs').then(m => ({ default: m.HelpApiDocsPage })));
const ApiDocsPage = React.lazy(() => import('@/pages/ApiDocs').then(m => ({ default: m.ApiDocsPage })));
/** Centered spinner fallback for lazy-loaded routes */
@@ -161,6 +165,7 @@ const router = createBrowserRouter([
{ path: 'navigation', element: withSuspense(
) },
{ path: 'contacts', element: withSuspense(
) },
{ path: 'mail-setup', element: withSuspense(
) },
+ { path: 'api-docs', element: withSuspense(
) },
{ path: '*', element: withSuspense(
) },
],
},
@@ -190,6 +195,14 @@ const router = createBrowserRouter([
{ path: '*', element: withSuspense(
) },
],
},
+ {
+ path: '/logs',
+ element: withSuspense(
),
+ children: [
+ { path: 'overview', element: withSuspense(
) },
+ { path: '*', element: withSuspense(
) },
+ ],
+ },
{
path: '/settings',
element:
{withSuspense( )} ,