diff --git a/src/routers/audit.py b/src/routers/audit.py index d7ed9f4..e651d7f 100644 --- a/src/routers/audit.py +++ b/src/routers/audit.py @@ -2,7 +2,7 @@ import json import logging from typing import Optional -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from auth import get_current_admin from database import db_dependency import aiosqlite @@ -25,7 +25,7 @@ async def list_audit( action: Optional[str] = None, resource_type: Optional[str] = None, resource_id: Optional[int] = None, - admin_id: Optional[int] = None, + admin_id: Optional[str] = None, from_ts: Optional[str] = None, to_ts: Optional[str] = None, limit: int = 200, @@ -33,7 +33,11 @@ async def list_audit( admin: dict = Depends(get_current_admin), db: aiosqlite.Connection = Depends(db_dependency), ): - """Audit-Log-Eintraege auflisten mit Filter + Pagination.""" + """Audit-Log-Eintraege auflisten mit Filter + Pagination. + + action, resource_type und admin_id akzeptieren kommagetrennte Mehrfachwerte + (Checkbox-Filter im Frontend), ein Einzelwert funktioniert weiterhin. + """ if limit < 1 or limit > 1000: limit = 200 if offset < 0: @@ -42,17 +46,26 @@ async def list_audit( where = [] params = [] if action: - where.append("action = ?") - params.append(action) + vals = [v.strip() for v in action.split(",") if v.strip()] + if vals: + where.append(f"action IN ({','.join('?' for _ in vals)})") + params.extend(vals) if resource_type: - where.append("resource_type = ?") - params.append(resource_type) + vals = [v.strip() for v in resource_type.split(",") if v.strip()] + if vals: + where.append(f"resource_type IN ({','.join('?' for _ in vals)})") + params.extend(vals) if resource_id is not None: where.append("resource_id = ?") params.append(resource_id) - if admin_id is not None: - where.append("admin_id = ?") - params.append(admin_id) + if admin_id: + try: + ids = [int(v) for v in admin_id.split(",") if v.strip()] + except ValueError: + raise HTTPException(status_code=422, detail="admin_id muss eine Zahl oder kommagetrennte Zahlen sein") + if ids: + where.append(f"admin_id IN ({','.join('?' for _ in ids)})") + params.extend(ids) if from_ts: where.append("ts >= ?") params.append(from_ts) diff --git a/src/static/css/style.css b/src/static/css/style.css index db7b9fc..ef7cee6 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -928,6 +928,49 @@ input[type="date"].filter-select { padding: 6px 10px; } .health-run-item { display: inline-flex; align-items: center; gap: 8px; } .health-run-item #healthRunInfo { font-size: 12px; } +/* Checkbox-Filterleisten (Standard fuer alle Filter im Portal) */ +.filter-bar { + display: flex; + flex-direction: column; + gap: 4px; + padding: 10px 14px; + margin-bottom: 14px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 8px; +} +.filter-group { + display: flex; + align-items: baseline; + gap: 4px 10px; + flex-wrap: wrap; +} +.filter-group-label { + font-size: 11px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.4px; + min-width: 92px; + cursor: default; +} +.filter-check { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 13px; + cursor: pointer; + padding: 2px 7px; + border-radius: 6px; + user-select: none; +} +.filter-check:hover { background: var(--bg-tertiary); } +.filter-check input { + accent-color: var(--accent); + cursor: pointer; + margin: 0; +} + /* Checkbox-Spalte: nur sichtbar bei Herkunfts-Filter "Kundenquellen" */ .select-col { display: none; } table.show-select .select-col { display: table-cell; } diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 5f80542..67a0bf7 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -6,7 +6,7 @@