← back to AbramsOS

views/audit.ejs

60 lines

<%- include('partials/header', { title: 'Audit log' }) %>

<section class="page-head">
  <h1>Audit log</h1>
  <p class="subtle" style="margin:0;color:var(--text-dim);font-size:13px">Every state-changing operation in AbramsOS lands here. Read-only.</p>
</section>

<section class="glass" style="padding:18px 22px">
  <div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;font-size:12px">
    <span style="color:var(--text-dim);margin-right:8px">Filter:</span>
    <a class="badge <%= !activeFilter ? 'active' : '' %>" href="/audit?since_days=<%= sinceDays %>" style="padding:3px 10px;border-radius:999px;background:<%= !activeFilter ? 'rgba(125,211,252,0.15)' : 'var(--surface-strong)' %>;color:<%= !activeFilter ? 'var(--accent)' : 'var(--text-dim)' %>;text-decoration:none">all</a>
    <% counts.forEach(c => { %>
      <a href="/audit?event_type=<%= encodeURIComponent(c.event_type) %>&since_days=<%= sinceDays %>" style="padding:3px 10px;border-radius:999px;background:<%= activeFilter === c.event_type ? 'rgba(125,211,252,0.15)' : 'var(--surface-strong)' %>;color:<%= activeFilter === c.event_type ? 'var(--accent)' : 'var(--text-dim)' %>;text-decoration:none">
        <%= c.event_type %> · <%= c.n %>
      </a>
    <% }) %>
  </div>
  <div style="margin-top:10px;font-size:11px;color:var(--text-dim)">
    Showing last <%= events.length %> events · last <%= sinceDays %> days
    <% if (sinceDays !== 7) { %><a href="/audit?since_days=7<%= activeFilter ? '&event_type=' + encodeURIComponent(activeFilter) : '' %>" style="margin-left:8px">7d</a><% } %>
    <% if (sinceDays !== 30) { %><a href="/audit?since_days=30<%= activeFilter ? '&event_type=' + encodeURIComponent(activeFilter) : '' %>" style="margin-left:8px">30d</a><% } %>
    <% if (sinceDays !== 90) { %><a href="/audit?since_days=90<%= activeFilter ? '&event_type=' + encodeURIComponent(activeFilter) : '' %>" style="margin-left:8px">90d</a><% } %>
  </div>
</section>

<% if (!events.length) { %>
  <section class="empty glass">
    <p>No events in this window. Use a wider time range or change the filter.</p>
  </section>
<% } else { %>
  <section class="glass" style="padding:0;overflow:hidden">
    <table style="width:100%;border-collapse:collapse;font-size:13px">
      <thead style="background:rgba(0,0,0,0.18);text-align:left">
        <tr style="color:var(--text-dim);font-size:11px;text-transform:uppercase;letter-spacing:0.5px">
          <th style="padding:10px 14px;font-weight:500">When</th>
          <th style="padding:10px 14px;font-weight:500">Event</th>
          <th style="padding:10px 14px;font-weight:500">Actor</th>
          <th style="padding:10px 14px;font-weight:500">Object</th>
          <th style="padding:10px 14px;font-weight:500">Metadata</th>
        </tr>
      </thead>
      <tbody>
        <% events.forEach(e => { %>
          <tr style="border-top:1px solid var(--border)">
            <td style="padding:8px 14px;color:var(--text-dim);white-space:nowrap;font-variant-numeric:tabular-nums"><%= new Date(e.occurred_at).toLocaleString() %></td>
            <td style="padding:8px 14px;font-weight:500"><%= e.event_type %></td>
            <td style="padding:8px 14px;color:var(--text-dim)"><%= e.actor_type %><% if (e.actor_id) { %>:<%= e.actor_id.slice(0, 16) %><% } %></td>
            <td style="padding:8px 14px;color:var(--text-dim)"><%= e.object_type %><% if (e.object_id) { %>:<%= e.object_id.slice(0, 16) %><% } %></td>
            <td style="padding:8px 14px;color:var(--text-dim);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;max-width:380px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
              <%= JSON.stringify(e.metadata_jsonb || {}) %>
            </td>
          </tr>
        <% }) %>
      </tbody>
    </table>
  </section>
<% } %>

<%- include('partials/footer') %>