feat(audit): admin audit log (Notification P3)
Append-only who/what/when trail for admin actions + scheduler run summaries, generalizing QuotaEvent. Logged at ACTION / run-summary granularity — never per-row (a scheduler run touching thousands of videos is ONE row). Backend: - migration 0054_audit_log + AuditLog model (actor_id FK SET NULL, action, target_type/id, summary, before/after JSON, created_at). - app/audit.py: AuditAction constants (single source of truth, i18n'd on the client) + record() (adds to the caller's txn) + gc(retention_days). - producers wired: role change / suspend / delete / invite approve-deny-add / demo add-remove-reset / discovery purge (admin.py); config set/reset — secret VALUES never logged, key only (config.py); scheduler interval + maintenance tune (scheduler routes); sync pause/resume (sync.py); and the scheduler _job() choke point writes ONE run-summary row per run — manual runs + errors always, automatic runs only when they changed something (no no-op-poll spam). - retention: audit_gc scheduler job prunes rows older than audit_retention_days (sysconfig, default 180; 0 = keep forever). - admin API routes/audit.py (/api/admin/audit): recent-window list (actor emails resolved, System for background) + clear (leaves one tamper-evidence row). Frontend: - new admin-only "Audit log" nav page (ScrollText) using the shared DataTable (first admin page to reuse it) — sort/filter/paginate/persist, action select filter, actor text filter, before→after detail, Clear-all with confirm. - AuditLog.tsx + auditColumns.tsx + api.adminAudit/clearAudit + AuditRow type. - trilingual audit + auditActions namespaces (HU/EN/DE) + nav + config-group + scheduler job labels; Audit config group (retention days).
This commit is contained in:
parent
c1d38eea21
commit
318fdc4812
35 changed files with 720 additions and 29 deletions
|
|
@ -926,6 +926,23 @@ export interface AdminUserRow {
|
|||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface AuditRow {
|
||||
id: number;
|
||||
created_at: string | null;
|
||||
actor: string | null; // email of the acting admin; null = System (scheduler/background)
|
||||
action: string; // canonical key, localized on the client via auditActions.<action>
|
||||
target_type: string | null;
|
||||
target_id: string | null;
|
||||
summary: string | null;
|
||||
before: Record<string, unknown> | null;
|
||||
after: Record<string, unknown> | null;
|
||||
}
|
||||
export interface AuditListResult {
|
||||
items: AuditRow[];
|
||||
total: number; // total rows in the log (may exceed `items` if capped)
|
||||
returned: number;
|
||||
}
|
||||
|
||||
// --- download center ---
|
||||
export interface DownloadSpec {
|
||||
mode: "av" | "v" | "a"; // audio+video | video-only | audio-only
|
||||
|
|
@ -1386,6 +1403,8 @@ export const api = {
|
|||
plexImageUrl: (id: string, variant?: "thumb" | "art"): string =>
|
||||
`/api/plex/image/${encodeURIComponent(id)}${variant === "art" ? "?variant=art" : ""}`,
|
||||
// --- admin: users & roles ---
|
||||
adminAudit: (limit = 500): Promise<AuditListResult> => req(`/api/admin/audit?limit=${limit}`),
|
||||
clearAudit: (): Promise<{ cleared: number }> => req("/api/admin/audit", { method: "DELETE" }),
|
||||
adminUsers: (): Promise<AdminUserRow[]> => req("/api/admin/users"),
|
||||
setUserRole: (id: number, role: "user" | "admin"): Promise<AdminUserRow> =>
|
||||
req(`/api/admin/users/${id}/role`, { method: "PATCH", body: JSON.stringify({ role }) }),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const LS = {
|
|||
channelDiscoveryTable: "siftlode.channelDiscoveryTable",
|
||||
settingsTab: "siftlode.settingsTab",
|
||||
configTab: "siftlode.configTab",
|
||||
auditTable: "siftlode.auditTable",
|
||||
statsTab: "siftlode.statsTab",
|
||||
adminUsersTab: "siftlode.adminUsersTab",
|
||||
navCollapsed: "siftlode.navCollapsed",
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ const PAGES = [
|
|||
"scheduler",
|
||||
"config",
|
||||
"users",
|
||||
"audit",
|
||||
"notifications",
|
||||
"messages",
|
||||
"downloads",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue