Files
Salus/app/templates/audit.html
Christoph Gasser 284924e86d Initial release — Salus by Stranto v1.6.1.0
FastAPI/Jinja2 web app for viewing and rebooting TP-Link Omada APs
across all sites. Authentik OIDC auth, SQLite audit log, Docker deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:36:02 +02:00

133 lines
6.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Audit Log Salus by Stranto{% endblock %}
{% block content %}
<div class="space-y-6">
<!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-gray-900">Audit Log</h1>
<p class="text-sm text-gray-500 mt-0.5">All reboot actions · {{ logs|length }} record{{ 's' if logs|length != 1 }}</p>
</div>
<a href="/audit/export"
class="inline-flex items-center gap-2 px-3 py-2 text-sm rounded-lg bg-blue-600 hover:bg-blue-500 border border-blue-500 text-white transition-colors self-start sm:self-auto">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
Export CSV
</a>
</div>
<!-- Filters -->
<form method="get" class="flex flex-col sm:flex-row gap-3">
<div class="relative flex-1">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
<input type="text" name="username" value="{{ filter_username }}"
placeholder="Filter by username…"
class="w-full pl-9 pr-3 py-2 text-sm rounded-lg bg-white border border-gray-300 text-gray-900 placeholder-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
</div>
<div class="relative flex-1">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.14 0"/>
</svg>
<input type="text" name="ap_name" value="{{ filter_ap }}"
placeholder="Filter by AP name…"
class="w-full pl-9 pr-3 py-2 text-sm rounded-lg bg-white border border-gray-300 text-gray-900 placeholder-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
</div>
<div class="flex gap-2">
<button type="submit"
class="px-4 py-2 text-sm rounded-lg bg-white hover:bg-gray-50 border border-gray-300 text-gray-700 transition-colors">
Filter
</button>
{% if filter_username or filter_ap %}
<a href="/audit"
class="px-4 py-2 text-sm rounded-lg bg-gray-50 hover:bg-gray-100 border border-gray-200 text-gray-500 transition-colors">
Clear
</a>
{% endif %}
</div>
</form>
{% if logs %}
<!-- Table -->
<div class="overflow-x-auto rounded-xl border border-gray-200 shadow-sm">
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Timestamp (UTC)</th>
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">User</th>
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">AP Name</th>
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">MAC</th>
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">IP</th>
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Result</th>
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Details</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{% for log in logs %}
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-4 py-3 text-gray-500 font-mono text-xs whitespace-nowrap">
{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
</td>
<td class="px-4 py-3">
<div class="text-gray-900 text-sm font-medium">{{ log.username }}</div>
<div class="text-gray-400 text-xs">{{ log.user_email }}</div>
</td>
<td class="px-4 py-3 text-gray-900 font-medium">{{ log.ap_name }}</td>
<td class="px-4 py-3 text-gray-500 font-mono text-xs">{{ log.ap_mac }}</td>
<td class="px-4 py-3 text-gray-500 font-mono text-xs">{{ log.ap_ip or '—' }}</td>
<td class="px-4 py-3">
{% if log.result == 'success' %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-green-50 text-green-700 border border-green-200">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>
</svg>
Success
</span>
{% else %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-red-50 text-red-700 border border-red-200">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/>
</svg>
Error
</span>
{% endif %}
</td>
<td class="px-4 py-3 text-gray-400 text-xs max-w-xs truncate" title="{{ log.error_message or '' }}">
{{ log.error_message or '—' }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="flex flex-col items-center justify-center py-20 text-gray-400">
<svg class="w-12 h-12 mb-4 opacity-40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<p class="text-lg font-medium">No log entries found</p>
{% if filter_username or filter_ap %}
<p class="text-sm mt-1">Try adjusting your filters.</p>
{% else %}
<p class="text-sm mt-1">Reboot actions will appear here.</p>
{% endif %}
</div>
{% endif %}
</div>
{% endblock %}