feat(admin): audit-log viewer
Surface the admin_audit table (written by every mutating admin action but previously unreadable) as a new /admin/audit tab. New repo::admin_audit::list (LEFT JOIN users for the actor's username, filter by action/target_kind/actor/ since, at DESC via admin_audit_at_idx) behind GET /v1/admin/audit, mirroring the crawler history endpoint's paged/clamped idiom. Frontend: a self-contained AuditTable (target-kind + window + action filters, expandable JSON payload, AbortController-cancelled fetches, loading/error/empty states) and shared fmtAgo() in format.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -978,3 +978,43 @@ export async function updateAnalysisSettings(
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
}
|
||||
|
||||
// ---- admin audit log -------------------------------------------------------
|
||||
|
||||
export type AuditEntry = {
|
||||
id: string;
|
||||
actor_user_id: string | null;
|
||||
/** Actor's current username; null when the account was deleted. */
|
||||
actor_username: string | null;
|
||||
action: string;
|
||||
target_kind: string;
|
||||
target_id: string | null;
|
||||
payload: unknown;
|
||||
at: string;
|
||||
};
|
||||
|
||||
export type AuditPage = { items: AuditEntry[]; page: Page };
|
||||
|
||||
export type ListAuditOptions = {
|
||||
action?: string;
|
||||
targetKind?: string;
|
||||
actorUserId?: string;
|
||||
days?: number;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export async function listAuditLog(
|
||||
opts: ListAuditOptions = {},
|
||||
init?: RequestInit
|
||||
): Promise<AuditPage> {
|
||||
const params = new URLSearchParams();
|
||||
if (opts.action) params.set('action', opts.action);
|
||||
if (opts.targetKind) params.set('target_kind', opts.targetKind);
|
||||
if (opts.actorUserId) params.set('actor_user_id', opts.actorUserId);
|
||||
if (opts.days != null && opts.days > 0) params.set('days', String(opts.days));
|
||||
if (opts.limit != null) params.set('limit', String(opts.limit));
|
||||
if (opts.offset != null) params.set('offset', String(opts.offset));
|
||||
const qs = params.toString();
|
||||
return request<AuditPage>(`/v1/admin/audit${qs ? `?${qs}` : ''}`, init);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user