126 lines
6.3 KiB
HTML
126 lines
6.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Audit Log{% endblock %}
|
|
{% block page_title %}Audit Log{% endblock %}
|
|
|
|
{% block topbar_actions %}
|
|
<a href="{{ url_for('settings.index') }}" class="btn btn-sm btn-outline-secondary me-1" style="font-size:12px;"><i class="bi bi-arrow-left me-1"></i>Settings</a>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="pcard pcard-sm mb-3">
|
|
<div class="d-flex gap-2 align-items-center flex-wrap">
|
|
<form method="GET" class="d-flex gap-2 align-items-center flex-wrap">
|
|
<select name="action" class="form-select form-select-sm" style="max-width:220px;">
|
|
<option value="">All events</option>
|
|
{% for a in actions %}
|
|
<option value="{{ a }}" {% if a == action %}selected{% endif %}>{{ a | replace('_',' ') | title }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="submit" class="btn btn-sm btn-outline-secondary"><i class="bi bi-filter me-1"></i>Filter</button>
|
|
{% if action %}<a href="{{ url_for('settings.audit_log') }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-x-lg"></i></a>{% endif %}
|
|
</form>
|
|
|
|
<div class="ms-auto">
|
|
<div class="dropdown">
|
|
<button class="btn btn-sm btn-outline-warning dropdown-toggle" type="button"
|
|
data-bs-toggle="dropdown" title="Purge old audit log entries">
|
|
<i class="bi bi-clock-history me-1"></i>Purge
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end" style="font-size:13px;">
|
|
<li><h6 class="dropdown-header">Delete entries older than…</h6></li>
|
|
<li>
|
|
<form method="POST" action="{{ url_for('settings.audit_purge') }}"
|
|
onsubmit="return confirm('Delete audit log entries older than 7 days?')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="days" value="7">
|
|
<button type="submit" class="dropdown-item">7 days</button>
|
|
</form>
|
|
</li>
|
|
<li>
|
|
<form method="POST" action="{{ url_for('settings.audit_purge') }}"
|
|
onsubmit="return confirm('Delete audit log entries older than 30 days?')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="days" value="30">
|
|
<button type="submit" class="dropdown-item">30 days</button>
|
|
</form>
|
|
</li>
|
|
<li>
|
|
<form method="POST" action="{{ url_for('settings.audit_purge') }}"
|
|
onsubmit="return confirm('Delete audit log entries older than 90 days?')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="days" value="90">
|
|
<button type="submit" class="dropdown-item">90 days</button>
|
|
</form>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pcard p-0">
|
|
{% if logs %}
|
|
<table class="pfm-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="padding-left:20px;">Time</th>
|
|
<th>Event</th>
|
|
<th class="d-mob-none">Details</th>
|
|
<th class="d-mob-none" style="padding-right:20px;">IP</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for entry in logs %}
|
|
<tr>
|
|
<td style="padding-left:20px;font-size:12px;color:var(--muted);white-space:nowrap;">
|
|
{{ entry.timestamp.strftime('%b %d, %H:%M:%S') }}
|
|
</td>
|
|
<td>
|
|
{% set colors = {
|
|
'login_success':'#d1fae5|#065f46',
|
|
'login_success_2fa':'#d1fae5|#065f46',
|
|
'login_failed':'#fee2e2|#991b1b',
|
|
'totp_enabled':'#dbeafe|#1e40af',
|
|
'totp_disabled':'#fef3c7|#92400e',
|
|
'schwab_connected':'#d1fae5|#065f46',
|
|
'schwab_disconnected':'#fee2e2|#991b1b',
|
|
'teller_connected':'#d1fae5|#065f46',
|
|
'teller_disconnected':'#fee2e2|#991b1b',
|
|
'password_changed':'#ede9fe|#5b21b6',
|
|
}.get(entry.action, '#f1f5f9|#475569').split('|') %}
|
|
<span style="font-size:12px;font-weight:600;background:{{ colors[0] }};color:{{ colors[1] }};padding:2px 8px;border-radius:4px;">
|
|
{{ entry.action | replace('_',' ') | title }}
|
|
</span>
|
|
</td>
|
|
<td class="d-mob-none" style="font-size:12px;color:var(--muted);">{{ entry.description or '—' }}</td>
|
|
<td class="d-mob-none" style="padding-right:20px;font-size:12px;color:var(--muted);">
|
|
<span class="mono">{{ entry.ip_address or '—' }}</span>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
{% if pagination.pages > 1 %}
|
|
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);">
|
|
<span>Page {{ pagination.page }} of {{ pagination.pages }} · {{ pagination.total }} events</span>
|
|
<div class="d-flex gap-1">
|
|
{% if pagination.has_prev %}
|
|
<a href="{{ url_for('settings.audit_log', page=pagination.prev_num, action=action) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a>
|
|
{% endif %}
|
|
{% if pagination.has_next %}
|
|
<a href="{{ url_for('settings.audit_log', page=pagination.next_num, action=action) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">Next →</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-shield-check text-muted" style="font-size:2.5rem;opacity:.4;"></i>
|
|
<p class="text-muted small mt-3 mb-0">No audit events recorded yet.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|