From 6b70a7195e3212f0ca2133f1583ab02b96dc972b Mon Sep 17 00:00:00 2001 From: claude-dev Date: Sat, 9 May 2026 03:19:32 +0000 Subject: [PATCH] Phase 5: Audit-Spur pro Quelle (ausklappbares Modal) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend - routers/audit.py: GET /api/audit-log nimmt jetzt resource_id als Filter (zusätzlich zu resource_type, action, admin_id, from_ts/to_ts). Frontend - dashboard.html: modalAudit (Modal) für die Audit-Spur einer Ressource. - style.css: audit-entry Styles (action-Badge mit Farbcode pro Action-Typ, Diff als
-Block mit JSON-Pre). - sources.js: - showSourceAudit(id, name) öffnet Modal, lädt /audit-log?resource_type=source&resource_id=... - renderAuditEntries: pro Eintrag Action-Badge + Meta (ts/admin/ip) + optional ausklappbarer Diff (before/after-JSON) - formatDateTime Helper - Audit-Button in der Aktionen-Spalte der Grundquellen-Tabelle --- src/routers/audit.py | 4 +++ src/static/css/style.css | 62 +++++++++++++++++++++++++++++++++++++++ src/static/dashboard.html | 14 +++++++++ src/static/js/sources.js | 51 ++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/src/routers/audit.py b/src/routers/audit.py index 405c700..a8582c5 100644 --- a/src/routers/audit.py +++ b/src/routers/audit.py @@ -24,6 +24,7 @@ def _parse_json(s: Optional[str]): async def list_audit( action: Optional[str] = None, resource_type: Optional[str] = None, + resource_id: Optional[int] = None, admin_id: Optional[int] = None, from_ts: Optional[str] = None, to_ts: Optional[str] = None, @@ -46,6 +47,9 @@ async def list_audit( if resource_type: where.append("resource_type = ?") params.append(resource_type) + 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) diff --git a/src/static/css/style.css b/src/static/css/style.css index aa4876d..c9047ac 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -872,3 +872,65 @@ input[type="date"].filter-select { padding: 6px 10px; } .health-badge-ok { background: rgba(16, 185, 129, 0.15); color: #10b981; } .health-badge-unknown { background: rgba(148, 163, 184, 0.15); color: #94a3b8; } +/* === Audit-Spur (Phase 5) === */ +.modal.modal-large { + max-width: 720px; +} +.audit-content { + max-height: 60vh; + overflow-y: auto; +} +.audit-entry { + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + padding: 10px 12px; + margin-bottom: 8px; + background: rgba(255, 255, 255, 0.02); +} +.audit-entry-head { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + font-size: 13px; +} +.audit-entry-action { + display: inline-block; + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.audit-action-create { background: rgba(16, 185, 129, 0.2); color: #10b981; } +.audit-action-update { background: rgba(59, 130, 246, 0.2); color: #3b82f6; } +.audit-action-delete { background: rgba(239, 68, 68, 0.2); color: #ef4444; } +.audit-action-login_success { background: rgba(16, 185, 129, 0.15); color: #10b981; } +.audit-action-login_failed { background: rgba(245, 158, 11, 0.15); color: #f59e0b; } +.audit-action-login_blocked { background: rgba(239, 68, 68, 0.2); color: #ef4444; } +.audit-entry-meta { + color: #94a3b8; + font-size: 12px; +} +.audit-entry-detail { + margin-top: 8px; +} +.audit-entry-detail summary { + cursor: pointer; + font-size: 12px; + color: #94a3b8; + user-select: none; +} +.audit-entry-detail pre { + margin: 6px 0 0 0; + padding: 8px 10px; + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + font-size: 11px; + line-height: 1.4; + overflow-x: auto; + color: #e2e8f0; + max-height: 240px; +} + diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 229c5be..6e6cb35 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -642,6 +642,20 @@ + + + +