feat(audit): filterable audit log page under Settings

The audit trail existed only as a summary card — no table, no filters.
The list endpoint was also self-scoped and never returned user_email
(the field existed on the struct but was never selected).

Backend: GET /audit-logs now joins users for email and accepts
user_id / actor (email ILIKE) / since (RFC3339) filters alongside the
existing resource/action/page/limit. Widening past self-scope matches
the platform's no-roles trust model — every authed user can already
create users. RLS on audit_logs is dormant (superuser connection,
app.current_user_id never set).

Frontend: /settings/audit-logs page with deferred filter inputs, time
range select, paginated table (time, actor, resource, action, id, IP,
details), linked from a Settings card. Spec updated and types
regenerated.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-17 16:36:05 +02:00
1 parent e127c62b0f
commit 2bfa683084
8 files changed
+337 -23

No files matched your search

@@ -50,6 +50,8 @@ import {
Unlink,
Cpu,
MemoryStick,
ScrollText,
ChevronRight,
} from 'lucide-react';
function SecondaryPageHeader({ title, description }: { title: string; description: string }) {
@@ -1057,6 +1059,29 @@ export function SettingsPage() {
{/* Git Providers Section */}
<GitProvidersSection />
{/* Audit Logs Section */}
<section className="panel p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-[var(--radius-md)] bg-[var(--accent-primary-soft)] flex items-center justify-center">
<ScrollText size={18} className="text-[var(--accent-primary)]" />
</div>
<div>
<h2 className="text-lg font-semibold text-[var(--text-primary)]">Audit Logs</h2>
<p className="text-xs text-[var(--text-tertiary)]">Every authenticated action recorded by the platform</p>
</div>
</div>
<p className="text-sm text-[var(--text-secondary)] mb-4">
Filter the full audit trail by resource, action, actor, and time range.
</p>
<button
onClick={() => navigate('/settings/audit-logs')}
className="flex items-center gap-2 h-9 px-4 rounded-[var(--radius-md)] border border-[var(--border-subtle)] text-sm font-medium hover:border-[var(--border-default)] transition-colors"
>
Open audit logs
<ChevronRight size={14} />
</button>
</section>
{/* Runtime & Local State Section */}
<section className="panel p-6">
<div className="flex items-center gap-3 mb-4">