diff --git a/backend/src/controllers/monitoring.controller.ts b/backend/src/controllers/monitoring.controller.ts index bca50281..ae22c3b4 100644 --- a/backend/src/controllers/monitoring.controller.ts +++ b/backend/src/controllers/monitoring.controller.ts @@ -148,6 +148,51 @@ export async function testAlert(_req: AuthRequest, res: Response): Promise } } +/** + * DELETE /api/monitoring/events + * Löscht alle SecurityEvents (oder optional nur älter als ?olderThanDays). + * Alert-versendete CRITICAL-Events werden vorher noch geloggt, damit der + * Audit-Trail erhalten bleibt. + */ +export async function clearEvents(req: AuthRequest, res: Response): Promise { + try { + const olderThanDays = req.query.olderThanDays + ? parseInt(req.query.olderThanDays as string) + : undefined; + + const where: any = {}; + if (olderThanDays && olderThanDays > 0) { + const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000); + where.createdAt = { lt: cutoff }; + } + + const result = await prisma.securityEvent.deleteMany({ where }); + + // Audit-Spur: Wer hat geleert + const user = (req as any).user; + await prisma.securityEvent.create({ + data: { + type: 'PERMISSION_CHANGED', + severity: 'INFO', + message: `Security-Log geleert: ${result.count} Einträge gelöscht${olderThanDays ? ` (älter als ${olderThanDays} Tage)` : ''}`, + userId: user?.userId || null, + userEmail: user?.email || null, + ipAddress: req.ip || 'unknown', + endpoint: 'DELETE /api/monitoring/events', + }, + }); + + res.json({ + success: true, + message: `${result.count} Events gelöscht`, + data: { deletedCount: result.count }, + } as any); + } catch (error) { + console.error('clearEvents error:', error); + res.status(500).json({ success: false, error: 'Löschen fehlgeschlagen' } as ApiResponse); + } +} + /** * POST /api/monitoring/run-digest (manueller Trigger für den Hourly-Digest) */ diff --git a/backend/src/routes/monitoring.routes.ts b/backend/src/routes/monitoring.routes.ts index 4c73a07f..c4720a5f 100644 --- a/backend/src/routes/monitoring.routes.ts +++ b/backend/src/routes/monitoring.routes.ts @@ -11,5 +11,6 @@ router.get('/settings', requirePermission('settings:read'), monitoringController router.put('/settings', requirePermission('settings:update'), monitoringController.updateMonitoringSettings); router.post('/test-alert', requirePermission('settings:update'), monitoringController.testAlert); router.post('/run-digest', requirePermission('settings:update'), monitoringController.runDigestNow); +router.delete('/events', requirePermission('settings:update'), monitoringController.clearEvents); export default router; diff --git a/frontend/src/pages/settings/Monitoring.tsx b/frontend/src/pages/settings/Monitoring.tsx index 288ba4b4..e84d59d4 100644 --- a/frontend/src/pages/settings/Monitoring.tsx +++ b/frontend/src/pages/settings/Monitoring.tsx @@ -7,7 +7,8 @@ import Card from '../../components/ui/Card'; import Button from '../../components/ui/Button'; import Input from '../../components/ui/Input'; import Select from '../../components/ui/Select'; -import { ArrowLeft, Send, RefreshCw, Mail, ShieldAlert, ChevronLeft, ChevronRight } from 'lucide-react'; +import Modal from '../../components/ui/Modal'; +import { ArrowLeft, Send, RefreshCw, Mail, ShieldAlert, ChevronLeft, ChevronRight, Trash2 } from 'lucide-react'; const TYPE_OPTIONS: { value: SecurityEventType | ''; label: string }[] = [ { value: '', label: 'Alle Typen' }, @@ -58,12 +59,15 @@ export default function Monitoring() { const queryClient = useQueryClient(); const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(50); const [filters, setFilters] = useState({ type: '' as SecurityEventType | '', severity: '' as SecuritySeverity | '', search: '', ip: '', }); + const [showClearConfirm, setShowClearConfirm] = useState(false); + const [clearOlderThanDays, setClearOlderThanDays] = useState(''); const [alertEmail, setAlertEmail] = useState(''); const [digestEnabled, setDigestEnabled] = useState(false); @@ -82,11 +86,22 @@ export default function Monitoring() { // Events laden const { data: eventsData, isLoading: eventsLoading } = useQuery({ - queryKey: ['monitoring-events', page, filters], - queryFn: () => monitoringApi.getEvents({ page, limit: 50, ...filters }), + queryKey: ['monitoring-events', page, pageSize, filters], + queryFn: () => monitoringApi.getEvents({ page, limit: pageSize, ...filters }), refetchInterval: 30_000, // alle 30s neu laden }); + const clearEvents = useMutation({ + mutationFn: (olderThanDays?: number) => monitoringApi.clearEvents(olderThanDays), + onSuccess: (res) => { + toast.success(res.message || 'Events gelöscht'); + setShowClearConfirm(false); + setClearOlderThanDays(''); + queryClient.invalidateQueries({ queryKey: ['monitoring-events'] }); + }, + onError: (e: Error) => toast.error(e.message || 'Löschen fehlgeschlagen'), + }); + const saveSettings = useMutation({ mutationFn: () => monitoringApi.updateSettings({ alertEmail, digestEnabled }), onSuccess: () => { @@ -227,7 +242,26 @@ export default function Monitoring() { {/* Tabelle */} -

Events

+
+

Events

+
+ + + +
+
{eventsLoading ? (
Lade…
) : events.length === 0 ? ( @@ -284,6 +318,50 @@ export default function Monitoring() { )}
+ + {/* Clear-Confirm-Modal */} + setShowClearConfirm(false)} + title="Security-Log leeren" + size="sm" + > +
+

+ Sicher? Alle Events werden aus der Datenbank entfernt. Ein + Audit-Log-Eintrag mit deinem Namen bleibt erhalten. +

+
+ + setClearOlderThanDays(e.target.value === '' ? '' : parseInt(e.target.value))} + placeholder="leer = alle löschen" + className="block w-full max-w-[200px] px-3 py-2 border border-gray-300 rounded text-sm" + /> +

+ Beispiel: 30 = nur Events älter als 30 Tage löschen. +

+
+
+ + +
+
+
); } diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index cadf8d6f..8312774c 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1489,6 +1489,11 @@ export const monitoringApi = { const res = await api.post>('/monitoring/run-digest'); return res.data; }, + clearEvents: async (olderThanDays?: number) => { + const q = olderThanDays ? `?olderThanDays=${olderThanDays}` : ''; + const res = await api.delete>(`/monitoring/events${q}`); + return res.data; + }, }; export const emailLogApi = {