freightdesk/webapp/src/views/pages/audit/list.ejs
FreightDesk 795cc86b5a [OWL] Audit logging: cherry-pick Hermes' audit SQL, add routes + views
From Hermes' agent/default/soft-delete-audit branch:
- Add migration 004_audit_logging.sql (audit_logs table, trigger function,
  triggers on loads/shippers/vehicles/payments/portal_users,
  set_audit_user() helper function)
- Improved: uses IF NOT EXISTS, AFTER triggers, user session context var,
  distinguishes SOFT_DELETE vs HARD_DELETE, notes field

New:
- GET /audit-logs (admin-only, filterable by table/action, paginated)
- GET /audit-logs/:id (detail view with before/after JSON)
- Audit Logs link in sidebar

Keeps all existing OWL code: CI/CD, Pino, Prometheus, tests, cache-busting,
debounced search, ESLint, Prettier
2026-06-07 20:03:23 +00:00

93 lines
3.9 KiB
Text

<%- include('../partials/header', { activeMenu: 'audit' }) %>
<div class="page-header">
<div>
<h1 class="page-title">&#128220; Audit Logs</h1>
<p class="page-subtitle">Track all changes across the platform</p>
</div>
</div>
<!-- Filters -->
<div class="card mb-4">
<div class="card-body">
<form method="GET" action="/audit-logs" class="filter-bar">
<div class="form-group">
<label class="form-label">Table</label>
<select name="table" class="form-input" onchange="this.form.submit()">
<option value="">All Tables</option>
<option value="loads" <%= filters.table === 'loads' ? 'selected' : '' %>>Loads</option>
<option value="shippers" <%= filters.table === 'shippers' ? 'selected' : '' %>>Shippers</option>
<option value="vehicles" <%= filters.table === 'vehicles' ? 'selected' : '' %>>Vehicles</option>
<option value="payments" <%= filters.table === 'payments' ? 'selected' : '' %>>Payments</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Action</label>
<select name="action" class="form-input" onchange="this.form.submit()">
<option value="">All Actions</option>
<option value="INSERT" <%= filters.action === 'INSERT' ? 'selected' : '' %>>Insert</option>
<option value="UPDATE" <%= filters.action === 'UPDATE' ? 'selected' : '' %>>Update</option>
<option value="SOFT_DELETE" <%= filters.action === 'SOFT_DELETE' ? 'selected' : '' %>>Soft Delete</option>
<option value="HARD_DELETE" <%= filters.action === 'HARD_DELETE' ? 'selected' : '' %>>Hard Delete</option>
</select>
</div>
<div class="form-group">
<label class="form-label">&nbsp;</label>
<a href="/audit-logs" class="btn btn-outline">Clear</a>
</div>
</form>
</div>
</div>
<!-- Logs Table -->
<div class="card">
<div class="card-body">
<% if (!logs || logs.length === 0) { %>
<p class="empty-state">No audit logs found matching your filters.</p>
<% } else { %>
<p class="text-muted mb-3">Showing <%= logs.length %> of <%= total %> logs (page <%= page %> of <%= totalPages %>)</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Time</th>
<th>Action</th>
<th>Table</th>
<th>Row ID</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<% for (const log of logs) { %>
<tr>
<td><%= new Date(log.created_at).toLocaleString('en-IN', { dateStyle: 'short', timeStyle: 'short' }) %></td>
<td>
<span class="badge badge-<%= log.action === 'INSERT' ? 'green' : log.action === 'UPDATE' ? 'blue' : log.action === 'SOFT_DELETE' ? 'orange' : 'red' %>">
<%= log.action %>
</span>
</td>
<td><code><%= log.table_name %></code></td>
<td><code><%= log.row_id ? log.row_id.slice(0,8) + '...' : '—' %></code></td>
<td><a href="/audit-logs/<%= log.id %>" class="btn btn-sm btn-outline">View</a></td>
</tr>
<% } %>
</tbody>
</table>
</div>
<!-- Pagination -->
<% if (totalPages > 1) { %>
<div class="pagination mt-3">
<% if (page > 1) { %>
<a href="/audit-logs?page=<%= page-1 %>&table=<%= filters.table || '' %>&action=<%= filters.action || '' %>" class="btn btn-sm btn-outline">&larr; Prev</a>
<% } %>
<span class="text-muted mx-2">Page <%= page %> of <%= totalPages %></span>
<% if (page < totalPages) { %>
<a href="/audit-logs?page=<%= page+1 %>&table=<%= filters.table || '' %>&action=<%= filters.action || '' %>" class="btn btn-sm btn-outline">Next &rarr;</a>
<% } %>
</div>
<% } %>
<% } %>
</div>
</div>
<%- include('../partials/footer') %>