vSphere-Backup-Manager/templates/audit_logs.html

241 lines
9.4 KiB
HTML

{% extends "base.html" %}
{% set active_page = 'audit-logs' %}
{% block title %}Audit Logs — vSphere Backup Manager{% endblock %}
{% block head %}
<style>
.history-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 24px;
box-shadow: var(--shadow);
backdrop-filter: blur(8px);
margin-bottom: 32px;
}
.search-input-wrap {
position: relative;
}
.search-input {
width: 100%;
padding: 8px 12px 8px 36px;
font-size: 13.5px;
}
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
pointer-events: none;
font-size: 14px;
}
.runs-table-wrap {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
overflow: hidden;
}
.runs-table-wrap table {
width: 100%;
border-collapse: collapse;
}
.runs-table-wrap th, .runs-table-wrap td {
padding: 12px 16px;
text-align: left;
}
.runs-table-wrap th {
background: rgba(255, 255, 255, 0.02);
border-bottom: 1px solid var(--border);
font-size: 11.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
}
.runs-table-wrap td {
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.runs-table-wrap tr:last-child td {
border-bottom: none;
}
.runs-table-wrap tr:hover td {
background: rgba(255, 255, 255, 0.01);
}
.text-right { text-align: right !important; }
.badge-action {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 10.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.action-user {
background: rgba(99, 102, 241, 0.15);
color: #818cf8;
border: 1px solid rgba(99, 102, 241, 0.3);
}
.action-system {
background: rgba(6, 182, 212, 0.15);
color: #22d3ee;
border: 1px solid rgba(6, 182, 212, 0.3);
}
.action-backup-success {
background: rgba(16, 185, 129, 0.15);
color: #34d399;
border: 1px solid rgba(16, 185, 129, 0.3);
}
.action-backup-fail {
background: rgba(239, 68, 68, 0.15);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
.clear-filter-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 16px;
border: 1px solid rgba(239, 68, 68, 0.4);
color: #f87171;
border-radius: var(--radius-sm);
font-size: 13px;
font-weight: 600;
text-decoration: none;
transition: background-color 0.2s, border-color 0.2s;
}
.clear-filter-btn:hover {
background: rgba(239, 68, 68, 0.1);
border-color: #ef4444;
}
</style>
{% endblock %}
{% block content %}
<div class="topbar">
<div>
<div class="topbar-title">Audit Logs</div>
<div class="topbar-subtitle">Historical records of all user management actions and system-level events (Auto-pruned after 360 days)</div>
</div>
</div>
<div class="content">
<div class="history-card">
<!-- Filters Toolbar -->
<form method="get" action="/audit-logs" style="margin-bottom: 20px;">
<div style="display: flex; gap: 12px; flex-wrap: wrap; align-items: center;">
<div class="search-input-wrap" style="flex: 1; min-width: 240px;">
<span class="search-icon">🔍</span>
<input type="text" name="q" value="{{ q or '' }}" class="form-control search-input" placeholder="Search actor, target, details..." />
</div>
<div style="display: flex; gap: 8px; align-items: center;">
<label style="font-size: 11px; color: var(--text-muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;">Type:</label>
<select name="actor_type" class="form-control" style="font-size: 13px; padding: 6px 12px; height: 36px; min-width: 130px; width: auto;" onchange="this.form.submit()">
<option value="all" {% if actor_type == 'all' %}selected{% endif %}>All Events</option>
<option value="user" {% if actor_type == 'user' %}selected{% endif %}>User Actions</option>
<option value="system" {% if actor_type == 'system' %}selected{% endif %}>System Events</option>
</select>
</div>
<div style="display: flex; gap: 8px; align-items: center;">
<label style="font-size: 11px; color: var(--text-muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;">Action:</label>
<select name="action_type" class="form-control" style="font-size: 13px; padding: 6px 12px; height: 36px; min-width: 165px; width: auto;" onchange="this.form.submit()">
<option value="all" {% if action_type == 'all' %}selected{% endif %}>All Actions</option>
{% for act in actions_list %}
<option value="{{ act }}" {% if action_type == act %}selected{% endif %}>{{ act }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-secondary" style="height: 36px;">Apply</button>
{% if q or actor_type != 'all' or action_type != 'all' %}
<a href="/audit-logs" class="clear-filter-btn" style="height: 36px;">Clear Filters</a>
{% endif %}
</div>
</form>
<!-- Table -->
<div class="runs-table-wrap">
<table>
<thead>
<tr>
<th style="width: 160px;">Timestamp</th>
<th style="width: 180px;">Actor</th>
<th style="width: 180px;">Action</th>
<th style="width: 140px;">Target</th>
<th>Details</th>
<th style="width: 130px;" class="text-right">IP Address</th>
</tr>
</thead>
<tbody>
{% if logs %}
{% for log in logs %}
<tr>
<td class="mono" style="font-size: 12px; color: var(--text-secondary);">{{ log.timestamp_fmt }}</td>
<td>
{% if log.actor == 'system' %}
<span style="font-weight: 600; color: #22d3ee;">🤖 system</span>
{% else %}
<span style="font-weight: 500; color: var(--text-primary);">👤 {{ log.actor }}</span>
{% endif %}
</td>
<td>
{% if log.action in ('USER_LOGIN', 'USER_LOGOUT') %}
<span class="badge-action action-user">{{ log.action }}</span>
{% elif 'SUCCESS' in log.action or 'OK' in log.action %}
<span class="badge-action action-backup-success">{{ log.action }}</span>
{% elif 'FAIL' in log.action or 'FAILURE' in log.action or 'ERROR' in log.action %}
<span class="badge-action action-backup-fail">{{ log.action }}</span>
{% else %}
<span class="badge-action action-system">{{ log.action }}</span>
{% endif %}
</td>
<td class="mono" style="font-size: 12px; color: var(--text-muted);">{{ log.target }}</td>
<td style="color: var(--text-secondary); word-break: break-word;">{{ log.details }}</td>
<td class="text-right mono" style="font-size: 12px; color: var(--text-muted);">{{ log.ip_address }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="6" style="text-align: center; padding: 48px; color: var(--text-muted);">
No matching audit logs found. User actions and system events will appear here chronologically.
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<!-- Pagination Controls -->
{% if total_pages > 1 %}
<div class="pagination-wrap" style="display: flex; justify-content: space-between; align-items: center; margin-top: 20px;">
<div style="font-size: 13px; color: var(--text-muted);">
Showing <strong>{{ logs|length }}</strong> of <strong>{{ total_count }}</strong> log entries
</div>
<div style="display: flex; gap: 8px; align-items: center;">
{% if page > 1 %}
<a href="{{ url_for('audit_logs_page', page=page-1, q=q, actor_type=actor_type, action_type=action_type) }}" class="btn btn-secondary btn-sm" style="padding: 6px 12px; display: inline-flex; align-items: center; justify-content: center; height: 32px; text-decoration: none;">← Previous</a>
{% else %}
<button class="btn btn-secondary btn-sm" disabled style="padding: 6px 12px; display: inline-flex; align-items: center; justify-content: center; height: 32px; opacity: 0.5; cursor: not-allowed;">← Previous</button>
{% endif %}
<span style="font-size: 13px; color: var(--text-secondary); margin: 0 4px;">Page <strong>{{ page }}</strong> of <strong>{{ total_pages }}</strong></span>
{% if page < total_pages %}
<a href="{{ url_for('audit_logs_page', page=page+1, q=q, actor_type=actor_type, action_type=action_type) }}" class="btn btn-secondary btn-sm" style="padding: 6px 12px; display: inline-flex; align-items: center; justify-content: center; height: 32px; text-decoration: none;">Next →</a>
{% else %}
<button class="btn btn-secondary btn-sm" disabled style="padding: 6px 12px; display: inline-flex; align-items: center; justify-content: center; height: 32px; opacity: 0.5; cursor: not-allowed;">Next →</button>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}