mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-06-04 13:30:32 +08:00
The system_settings update, system admin promote/revoke, and
apply-default-storage-quota routes have been writing audit rows since
the prior commits, but with TenantID=0 (system-scope). The per-tenant
GET /tenants/:id/audit-log endpoint filters by tenant_id and never
returns them, so until now those rows existed only in the DB with no UI
surface. This commit closes the loop:
Backend:
- GET /api/v1/system/admin/audit-log: new SystemAdmin-gated endpoint
reusing AuditLogService.List with tenant_id=0. Same cursor-paged
shape and query params (after_id / limit / action / outcome / actor)
as the per-tenant feed, so the frontend reuses the same client logic.
- Wired through RegisterSystemAdminRoutes (mounted on the existing
adminRoutes group so it inherits the SystemAdmin() guard). The
handler dependency is optional: nil auditLogHandler skips the route,
mirroring RegisterTenantRoutes' /audit-log handling.
Frontend — new platform audit drawer in SystemSettings.vue:
- "审计日志" entry button in the section header opens a side drawer
(880px) listing system-scope events. Lazy-loaded on first open;
refresh is explicit via a button inside the drawer.
- Table columns: stacked date/time (so 50 events in the same minute
remain distinguishable), stacked actor/role, action tag, structured
target (subject key + diff line), outcome. The dead request column
(system actions don't go through middleware path capture) is dropped
in favour of richer target rendering.
- Per-action target formatters:
* system.setting_changed: subject = registry key, diff = `old → new`
(JSON-encoded, 80-char truncation). Reset shows `old → (空)`.
* tenant_storage_quota bulk apply: subject = "批量同步", diff =
"applied to N tenants (X GB)".
* system.admin_promoted / revoked: subject = "name (email)", diff
annotates idempotent / noop branches so an audit reader can tell
a real grant from a probe.
- Click-to-expand row reveals the full audit context: actor UUID,
target_user_id / target_type / target_id, and raw details JSON in
monospaced scroll-capped block. No psql round-trip needed for
forensic spot checks.
- Sticky thead pinned to the scroll container so column labels survive
long scrolls. Cells vertical-aligned middle to keep single-line tag
cells visually balanced against multi-line target cells. No zebra
stripes — the stacked content already provides row separation, and
stripes on top read as noise.
Frontend — same polish back-ported to TenantMembers.vue audit drawer:
- Same drawer width, stacked time / actor cells, structured target +
diff layout, expandable raw-details row, sticky thead, vertical-
align middle, no stripes. Refresh button reformulated as a text
button with label (was an outlined square icon-only).
- request_path column kept (rbac.access_denied carries meaningful
paths) but empty values render as a placeholder dash so they don't
read as broken.
- Diff line now covers rbac.invitation_sent / invitation_revoked role
in addition to the existing role_changed / access_denied details.
API:
- frontend/src/api/system/index.ts: listSystemAuditLog() reuses the
AuditLog / ListAuditLogParams types from @/api/tenant/audit-log
(re-exported) so consumers don't need to cross-import.
i18n (zh-CN / en-US / ko-KR / ru-RU):
- system.globalSettings.audit.*: full drawer copy + per-action labels
(system.setting_changed / admin_promoted / admin_revoked) + target
diff templates + expanded-row labels.
- tenantMember.audit.expanded.*: expanded-row labels added so the
shared drawer treatment renders cleanly under tenant scope.