{{define "title"}}Audit · restic-manager{{end}}

{{define "content"}}
{{$page := .Page}}
{{$filter := $page.Filter}}
{{$rng := $page.Range}}
<div class="max-w-[1280px] mx-auto px-8 pb-14">

  {{/* crumbs */}}
  <div class="crumbs pt-6">
    <a href="/">Dashboard</a><span class="sep">/</span>
    <span class="text-ink-mid">audit</span>
  </div>

  {{/* page header */}}
  <div class="flex items-baseline justify-between mt-3.5">
    <div>
      <h1 class="text-[22px] font-medium tracking-[-0.005em]">
        Audit log
        <span class="text-ink-fade font-normal text-[14px] ml-2">
          {{len $page.Entries}} entries · last {{if eq $rng "all"}}all-time{{else}}{{$rng}}{{end}}
        </span>
      </h1>
    </div>
    <div class="flex gap-2">
      {{/* Export carries the current filter querystring so the
           download is exactly what the operator sees on screen
           (up to a higher row cap of 5000 vs 500 in the table). */}}
      <a href="{{$page.CSVHref}}"
         class="btn"
         title="Download the current filter as CSV (up to 5000 rows, UTF-8, RFC 4180)">
        Export CSV ↓
      </a>
    </div>
  </div>

  <div class="text-ink-mute mt-2 leading-[1.55]" style="font-size: 11.5px; max-width: 760px;">
    Append-only history of every operator action, agent message, and system-driven change.
    Read-only — entries cannot be edited or deleted.
  </div>

  {{/* filter strip */}}
  <div class="panel mt-4 px-4 py-3 rounded-[7px]"
       style="display: grid; grid-template-columns: auto auto auto auto 1fr; gap: 14px; align-items: center;">

    {{/* time-range pills */}}
    <div class="inline-flex gap-1 p-[3px]" style="border: 1px solid var(--line-soft); border-radius: 5px;">
      {{range list "24h" "7d" "30d" "all"}}
        {{$r := .}}
        {{$active := eq $r $rng}}
        <a href="/audit?range={{$r}}{{if $filter.UserID}}&user_id={{$filter.UserID}}{{end}}{{if $filter.Actor}}&actor={{$filter.Actor}}{{end}}{{if $filter.ActionLike}}&action={{$filter.ActionLike}}{{end}}{{if $filter.TargetKind}}&target_kind={{$filter.TargetKind}}{{end}}"
           class="btn btn-ghost"
           style="padding: 5px 10px; font-size: 11.5px;{{if $active}} background: var(--panel-hi); color: var(--ink);{{end}}">
          {{if eq $r "all"}}All{{else}}{{$r}}{{end}}
        </a>
      {{end}}
    </div>

    {{/* user dropdown */}}
    <div>
      <select class="field" style="padding: 6px 10px; font-size: 11.5px; min-width: 140px;"
              onchange="window.location='/audit?range={{$rng}}&user_id='+this.value+'{{if $filter.Actor}}&actor={{$filter.Actor}}{{end}}{{if $filter.ActionLike}}&action={{$filter.ActionLike}}{{end}}{{if $filter.TargetKind}}&target_kind={{$filter.TargetKind}}{{end}}'">
        <option value="" {{if eq $filter.UserID ""}}selected{{end}}>User · any</option>
        {{range $id, $name := $page.UserNames}}
          <option value="{{$id}}" {{if eq $filter.UserID $id}}selected{{end}}>{{$name}}</option>
        {{end}}
      </select>
    </div>

    {{/* actor dropdown — user/agent/system */}}
    <div>
      <select class="field" style="padding: 6px 10px; font-size: 11.5px; min-width: 130px;"
              onchange="window.location='/audit?range={{$rng}}{{if $filter.UserID}}&user_id={{$filter.UserID}}{{end}}&actor='+this.value+'{{if $filter.ActionLike}}&action={{$filter.ActionLike}}{{end}}{{if $filter.TargetKind}}&target_kind={{$filter.TargetKind}}{{end}}'">
        <option value="" {{if eq $filter.Actor ""}}selected{{end}}>Actor · any</option>
        <option value="user"   {{if eq $filter.Actor "user"}}selected{{end}}>user</option>
        <option value="agent"  {{if eq $filter.Actor "agent"}}selected{{end}}>agent</option>
        <option value="system" {{if eq $filter.Actor "system"}}selected{{end}}>system</option>
      </select>
    </div>

    {{/* target kind dropdown */}}
    <div>
      <select class="field" style="padding: 6px 10px; font-size: 11.5px; min-width: 160px;"
              onchange="window.location='/audit?range={{$rng}}{{if $filter.UserID}}&user_id={{$filter.UserID}}{{end}}{{if $filter.Actor}}&actor={{$filter.Actor}}{{end}}{{if $filter.ActionLike}}&action={{$filter.ActionLike}}{{end}}&target_kind='+this.value">
        <option value="" {{if eq $filter.TargetKind ""}}selected{{end}}>Target · any</option>
        <option value="host"                 {{if eq $filter.TargetKind "host"}}selected{{end}}>host</option>
        <option value="schedule"             {{if eq $filter.TargetKind "schedule"}}selected{{end}}>schedule</option>
        <option value="source_group"         {{if eq $filter.TargetKind "source_group"}}selected{{end}}>source_group</option>
        <option value="alert"                {{if eq $filter.TargetKind "alert"}}selected{{end}}>alert</option>
        <option value="notification_channel" {{if eq $filter.TargetKind "notification_channel"}}selected{{end}}>notification_channel</option>
        <option value="job"                  {{if eq $filter.TargetKind "job"}}selected{{end}}>job</option>
        <option value="user"                 {{if eq $filter.TargetKind "user"}}selected{{end}}>user</option>
      </select>
    </div>

    {{/* action substring search */}}
    <form method="get" action="/audit">
      <input type="hidden" name="range" value="{{$rng}}">
      {{if $filter.UserID}}<input type="hidden" name="user_id" value="{{$filter.UserID}}">{{end}}
      {{if $filter.Actor}}<input type="hidden" name="actor" value="{{$filter.Actor}}">{{end}}
      {{if $filter.TargetKind}}<input type="hidden" name="target_kind" value="{{$filter.TargetKind}}">{{end}}
      <input type="text" name="action" value="{{$filter.ActionLike}}"
             placeholder="action contains… (e.g. alert., host.)"
             class="field mono"
             style="padding: 6px 10px; font-size: 11.5px;">
    </form>
  </div>

  {{/* table */}}
  <div class="panel mt-3.5 rounded-[7px] overflow-hidden">

    {{/* Header — every column except the payload one is a clickable
         sort link. Hrefs are pre-built server-side ($page.SortHrefs)
         so the URL escaping rules don't trip on the '=' chars when
         html/template encodes <a href> attributes. */}}
    <div class="audit-row head">
      <div>
        <a href="{{index $page.SortHrefs "ts"}}"
           class="sort-header">When <span class="sort-glyph">{{sortGlyph "ts" $page.Sort $page.Dir}}</span></a>
      </div>
      <div>
        <a href="{{index $page.SortHrefs "actor"}}"
           class="sort-header">Actor <span class="sort-glyph">{{sortGlyph "actor" $page.Sort $page.Dir}}</span></a>
      </div>
      <div>
        <a href="{{index $page.SortHrefs "user_id"}}"
           class="sort-header">User <span class="sort-glyph">{{sortGlyph "user_id" $page.Sort $page.Dir}}</span></a>
      </div>
      <div>
        <a href="{{index $page.SortHrefs "action"}}"
           class="sort-header">Action <span class="sort-glyph">{{sortGlyph "action" $page.Sort $page.Dir}}</span></a>
      </div>
      <div>
        <a href="{{index $page.SortHrefs "target_kind"}}"
           class="sort-header">Target <span class="sort-glyph">{{sortGlyph "target_kind" $page.Sort $page.Dir}}</span></a>
      </div>
      <div></div>
    </div>

    {{if eq (len $page.Entries) 0}}
      <div style="padding: 40px; text-align: center;">
        <div class="text-ink text-[14px] font-medium">No matching entries.</div>
        <div class="text-ink-mute text-[12px] mt-1">
          {{if eq $rng "24h"}}Try widening the time range.{{else}}Adjust filters or pick a longer range.{{end}}
        </div>
      </div>
    {{else}}
      {{range $page.Entries}}
        {{$e := .}}
        <div class="audit-row">
          <div class="mono text-[12px] text-ink-mute" title="UTC">
            {{absTime $e.TS}}
          </div>
          <div>
            {{if eq $e.Actor "user"}}<span class="tag" style="background: color-mix(in oklch, var(--accent), transparent 92%); border-color: color-mix(in oklch, var(--accent), transparent 60%); color: var(--accent);">user</span>
            {{else if eq $e.Actor "agent"}}<span class="tag" style="background: color-mix(in oklch, var(--ok), transparent 92%); border-color: color-mix(in oklch, var(--ok), transparent 60%); color: var(--ok);">agent</span>
            {{else}}<span class="tag" style="background: color-mix(in oklch, var(--ink-fade), transparent 92%); color: var(--ink-mute);">system</span>{{end}}
          </div>
          <div class="mono text-[12px] text-ink-mid">
            {{if $e.UserID}}{{$un := index $page.UserNames (deref $e.UserID)}}{{if $un}}{{$un}}{{else}}<span class="text-ink-fade">{{deref $e.UserID}}</span>{{end}}{{else}}<span class="text-ink-fade">—</span>{{end}}
          </div>
          <div class="mono text-[12px] text-ink">{{$e.Action}}</div>
          <div class="mono text-[12px] text-ink-mute">
            {{if $e.TargetKind}}
              <span class="text-ink-fade">{{deref $e.TargetKind}}</span>
              {{if $e.TargetID}}
                {{$tid := deref $e.TargetID}}
                {{if eq (deref $e.TargetKind) "host"}}{{$hn := index $page.HostNames $tid}}{{if $hn}} · {{$hn}}{{else}} · {{$tid}}{{end}}
                {{else}} · {{$tid}}{{end}}
              {{end}}
            {{else}}
              <span class="text-ink-fade">—</span>
            {{end}}
          </div>
          <div class="text-right">
            {{if and $e.Payload (gt (len $e.Payload) 2)}}
              {{/* Payload is base64-encoded onto a data- attribute to
                   bypass html/template's contextual JS-string escaping
                   (which would double-escape arbitrary JSON inside a
                   <script type="application/json"> block). Decoded by
                   atob() in the modal opener. */}}
              <button type="button" class="btn"
                      style="font-size: 11px; padding: 3px 8px;"
                      data-payload-action="{{$e.Action}}"
                      data-payload-id="{{$e.ID}}"
                      data-payload-b64="{{b64 $e.Payload}}"
                      onclick="window.__rmAuditOpenPayload(this)">payload ↗</button>
            {{end}}
          </div>
        </div>
      {{end}}
    {{end}}

  </div>

  {{/* Payload modal — single instance shared by every row. Centred
       overlay with a max-height; the inner <pre> scrolls when the
       payload is long. Closes on backdrop click, Escape key, or the
       × button. Plain JSON is pretty-printed; non-JSON falls back to
       the raw string. */}}
  <div id="audit-payload-modal" class="fixed inset-0 z-50 hidden"
       style="background: rgba(0,0,0,0.55); align-items: center; justify-content: center;"
       onclick="if (event.target === this) window.__rmAuditClosePayload()">
    <div class="panel rounded-[7px]"
         style="width: min(720px, 90vw); max-height: 80vh; display: flex; flex-direction: column;"
         onclick="event.stopPropagation()">
      <div class="flex items-center justify-between"
           style="padding: 14px 18px; border-bottom: 1px solid var(--line-soft);">
        <div>
          <div class="text-[13px] font-medium text-ink" id="audit-payload-title">payload</div>
          <div class="text-[11px] text-ink-fade mono mt-0.5" id="audit-payload-subtitle"></div>
        </div>
        <div class="flex gap-2">
          <button type="button" class="btn"
                  style="font-size: 11.5px;"
                  onclick="window.__rmAuditCopyPayload()">Copy</button>
          <button type="button" class="btn"
                  style="font-size: 11.5px;"
                  onclick="window.__rmAuditClosePayload()">×</button>
        </div>
      </div>
      <pre id="audit-payload-body" class="mono text-[12px] text-ink-mid"
           style="margin: 0; padding: 16px 18px; overflow: auto; white-space: pre-wrap; word-break: break-all; flex: 1; background: var(--bg);"></pre>
    </div>
  </div>

</div>

<script>
(function() {
  var modal    = document.getElementById('audit-payload-modal');
  var bodyEl   = document.getElementById('audit-payload-body');
  var titleEl  = document.getElementById('audit-payload-title');
  var subEl    = document.getElementById('audit-payload-subtitle');
  var current  = '';

  window.__rmAuditOpenPayload = function(btn) {
    var id     = btn.getAttribute('data-payload-id');
    var action = btn.getAttribute('data-payload-action');
    var b64    = btn.getAttribute('data-payload-b64') || '';
    var raw    = '';
    try { raw = atob(b64); } catch (e) { raw = ''; }
    try {
      current = JSON.stringify(JSON.parse(raw), null, 2);
    } catch (e) {
      current = raw;
    }
    bodyEl.textContent = current;
    titleEl.textContent = action;
    subEl.textContent   = id;
    modal.style.display = 'flex';
    modal.classList.remove('hidden');
  };
  window.__rmAuditClosePayload = function() {
    modal.classList.add('hidden');
    modal.style.display = 'none';
  };
  window.__rmAuditCopyPayload = function() {
    if (!current) return;
    navigator.clipboard.writeText(current).catch(function() {});
  };
  document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
      window.__rmAuditClosePayload();
    }
  });
})();
</script>
{{end}}
