'use client' import { useState, useEffect } from 'react' interface AuditLogEntry { id: string action: string entity_type: string entity_id?: string old_value?: any new_value?: any user_email?: string created_at: string } interface BlockedContentEntry { id: string url: string domain: string block_reason: string rule_id?: string details?: any created_at: string } interface AuditTabProps { apiBase: string } const ACTION_LABELS: Record = { create: { label: 'Erstellt', color: 'bg-green-100 text-green-700' }, update: { label: 'Aktualisiert', color: 'bg-blue-100 text-blue-700' }, delete: { label: 'Geloescht', color: 'bg-red-100 text-red-700' }, } const ENTITY_LABELS: Record = { source_policy: 'Policy', allowed_source: 'Quelle', operation_permission: 'Operation', pii_rule: 'PII-Regel', } const BLOCK_REASON_LABELS: Record = { not_whitelisted: { label: 'Nicht in Whitelist', color: 'bg-amber-100 text-amber-700' }, pii_detected: { label: 'PII erkannt', color: 'bg-red-100 text-red-700' }, license_violation: { label: 'Lizenzverletzung', color: 'bg-orange-100 text-orange-700' }, training_forbidden: { label: 'Training verboten', color: 'bg-slate-800 text-white' }, } export function AuditTab({ apiBase }: AuditTabProps) { const [activeView, setActiveView] = useState<'changes' | 'blocked'>('changes') // Audit logs const [auditLogs, setAuditLogs] = useState([]) const [auditLoading, setAuditLoading] = useState(true) const [auditTotal, setAuditTotal] = useState(0) // Blocked content const [blockedContent, setBlockedContent] = useState([]) const [blockedLoading, setBlockedLoading] = useState(true) const [blockedTotal, setBlockedTotal] = useState(0) // Filters const [dateFrom, setDateFrom] = useState('') const [dateTo, setDateTo] = useState('') const [entityFilter, setEntityFilter] = useState('') // Export const [exporting, setExporting] = useState(false) const [error, setError] = useState(null) useEffect(() => { if (activeView === 'changes') { fetchAuditLogs() } else { fetchBlockedContent() } }, [activeView, dateFrom, dateTo, entityFilter]) const fetchAuditLogs = async () => { try { setAuditLoading(true) const params = new URLSearchParams() if (dateFrom) params.append('from', dateFrom) if (dateTo) params.append('to', dateTo) if (entityFilter) params.append('entity_type', entityFilter) params.append('limit', '100') const res = await fetch(`${apiBase}/v1/admin/policy-audit?${params}`) if (!res.ok) throw new Error('Fehler beim Laden') const data = await res.json() setAuditLogs(data.logs || []) setAuditTotal(data.total || 0) } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setAuditLoading(false) } } const fetchBlockedContent = async () => { try { setBlockedLoading(true) const params = new URLSearchParams() if (dateFrom) params.append('from', dateFrom) if (dateTo) params.append('to', dateTo) params.append('limit', '100') const res = await fetch(`${apiBase}/v1/admin/blocked-content?${params}`) if (!res.ok) throw new Error('Fehler beim Laden') const data = await res.json() setBlockedContent(data.blocked || []) setBlockedTotal(data.total || 0) } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setBlockedLoading(false) } } const exportReport = async () => { try { setExporting(true) const params = new URLSearchParams() if (dateFrom) params.append('from', dateFrom) if (dateTo) params.append('to', dateTo) params.append('format', 'download') const res = await fetch(`${apiBase}/v1/admin/compliance-report?${params}`) if (!res.ok) throw new Error('Fehler beim Export') const blob = await res.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `compliance-report-${new Date().toISOString().split('T')[0]}.json` document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) document.body.removeChild(a) } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setExporting(false) } } const formatDate = (dateStr: string) => { const date = new Date(dateStr) return date.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }) } return (
{/* Error Display */} {error && (
{error}
)} {/* View Toggle & Filters */}
setDateFrom(e.target.value)} className="px-3 py-2 border border-slate-200 rounded-lg text-sm" />
setDateTo(e.target.value)} className="px-3 py-2 border border-slate-200 rounded-lg text-sm" />
{activeView === 'changes' && ( )}
{/* Changes View */} {activeView === 'changes' && ( <> {auditLoading ? (
Lade Audit-Log...
) : auditLogs.length === 0 ? (

Keine Eintraege vorhanden

Aenderungen werden hier protokolliert.

) : (
{auditTotal} Eintraege gesamt
{auditLogs.map((log) => { const actionConfig = ACTION_LABELS[log.action] || { label: log.action, color: 'bg-slate-100 text-slate-700' } return (
{actionConfig.label} {ENTITY_LABELS[log.entity_type] || log.entity_type} {log.entity_id && ( {log.entity_id.substring(0, 8)}... )}
{formatDate(log.created_at)}
{log.user_email && (
Benutzer: {log.user_email}
)} {(log.old_value || log.new_value) && (
{log.old_value && (
Vorher:
                                {typeof log.old_value === 'string' ? log.old_value : JSON.stringify(log.old_value, null, 2)}
                              
)} {log.new_value && (
Nachher:
                                {typeof log.new_value === 'string' ? log.new_value : JSON.stringify(log.new_value, null, 2)}
                              
)}
)}
) })}
)} )} {/* Blocked Content View */} {activeView === 'blocked' && ( <> {blockedLoading ? (
Lade blockierte URLs...
) : blockedContent.length === 0 ? (

Keine blockierten URLs

Alle gecrawlten URLs waren in der Whitelist.

) : (
{blockedTotal} blockierte URLs gesamt
{blockedContent.map((entry) => { const reasonConfig = BLOCK_REASON_LABELS[entry.block_reason] || { label: entry.block_reason, color: 'bg-slate-100 text-slate-700', } return ( ) })}
URL Domain Grund Zeitpunkt
{entry.url}
{entry.domain} {reasonConfig.label} {formatDate(entry.created_at)}
)} )} {/* Auditor Info Box */}

Fuer Auditoren

Dieses Audit-Log ist unveraenderlich und protokolliert alle Aenderungen an der Quellen-Policy. Jeder Eintrag enthaelt:

  • Zeitstempel der Aenderung
  • Art der Aenderung (Erstellen/Aendern/Loeschen)
  • Betroffene Entitaet und ID
  • Vorheriger und neuer Wert
  • E-Mail des Benutzers (falls angemeldet)

Der JSON-Export ist fuer die externe Pruefung und Archivierung geeignet.

) }