Monitoring UX: Log leeren + PageSize wählbar
- Backend: DELETE /api/monitoring/events (settings:update). Optional ?olderThanDays=N – nur Events älter als N Tage löschen. Hinterlässt selbst einen Audit-Eintrag "Log geleert: X Einträge" mit User-E-Mail + IP, damit der Vorgang nachvollziehbar bleibt. - Frontend: "Log leeren"-Button öffnet Bestätigungs-Modal mit optionalem "älter als X Tage"-Filter. Roter Bestätigungs-Button. - Frontend: PageSize-Selector (10/25/50/100/200) neben dem Header. Wechsel setzt automatisch zurück auf Seite 1. Live-verifiziert: Clear löscht 10 Events, schreibt 1 Audit-Event, PageSize=5 wird in pagination respektiert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<number | ''>('');
|
||||
|
||||
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 */}
|
||||
<Card>
|
||||
<h2 className="text-lg font-semibold mb-3">Events</h2>
|
||||
<div className="flex items-center justify-between mb-3 flex-wrap gap-2">
|
||||
<h2 className="text-lg font-semibold">Events</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm text-gray-600">Pro Seite:</label>
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={(e) => { setPageSize(parseInt(e.target.value)); setPage(1); }}
|
||||
className="px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={100}>100</option>
|
||||
<option value={200}>200</option>
|
||||
</select>
|
||||
<Button variant="secondary" size="sm" onClick={() => setShowClearConfirm(true)}>
|
||||
<Trash2 className="w-4 h-4 mr-1" /> Log leeren
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{eventsLoading ? (
|
||||
<div className="text-gray-500 py-4">Lade…</div>
|
||||
) : events.length === 0 ? (
|
||||
@@ -284,6 +318,50 @@ export default function Monitoring() {
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Clear-Confirm-Modal */}
|
||||
<Modal
|
||||
isOpen={showClearConfirm}
|
||||
onClose={() => setShowClearConfirm(false)}
|
||||
title="Security-Log leeren"
|
||||
size="sm"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-700">
|
||||
Sicher? Alle Events werden aus der Datenbank entfernt. Ein
|
||||
Audit-Log-Eintrag mit deinem Namen bleibt erhalten.
|
||||
</p>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nur Events älter als (Tage)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={clearOlderThanDays}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Beispiel: 30 = nur Events älter als 30 Tage löschen.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="secondary" onClick={() => setShowClearConfirm(false)}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => clearEvents.mutate(clearOlderThanDays === '' ? undefined : Number(clearOlderThanDays))}
|
||||
disabled={clearEvents.isPending}
|
||||
className="!bg-red-600 hover:!bg-red-700"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-1" />
|
||||
{clearOlderThanDays === '' ? 'Alle löschen' : `Älter als ${clearOlderThanDays} Tage löschen`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1489,6 +1489,11 @@ export const monitoringApi = {
|
||||
const res = await api.post<ApiResponse<{ sent: boolean; eventCount: number; reason?: string }>>('/monitoring/run-digest');
|
||||
return res.data;
|
||||
},
|
||||
clearEvents: async (olderThanDays?: number) => {
|
||||
const q = olderThanDays ? `?olderThanDays=${olderThanDays}` : '';
|
||||
const res = await api.delete<ApiResponse<{ deletedCount: number }>>(`/monitoring/events${q}`);
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
|
||||
export const emailLogApi = {
|
||||
|
||||
Reference in New Issue
Block a user