2025-08-12 13:33:25 +03:00

267 lines
9.3 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Audit Log - {{ block.super }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Page Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1">
<i class="fas fa-history me-2"></i>Audit Log
</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item active">Audit Log</li>
</ol>
</nav>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-download me-2"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportAuditLog('csv')">
<i class="fas fa-file-csv me-2"></i>Export as CSV
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportAuditLog('excel')">
<i class="fas fa-file-excel me-2"></i>Export as Excel
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportAuditLog('pdf')">
<i class="fas fa-file-pdf me-2"></i>Export as PDF
</a></li>
</ul>
<button type="button" class="btn btn-primary" onclick="refreshAuditLog()">
<i class="fas fa-sync-alt me-2"></i>Refresh
</button>
</div>
</div>
<!-- Filters Card -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-filter me-2"></i>Filters
</h5>
</div>
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-3">
<label for="event_type" class="form-label">Event Type</label>
<select name="event_type" id="event_type" class="form-select">
<option value="">All Types</option>
{% for event_type in event_types %}
<option value="{{ event_type }}" {% if request.GET.event_type == event_type %}selected{% endif %}>
{{ event_type|title }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label for="event_category" class="form-label">Category</label>
<select name="event_category" id="event_category" class="form-select">
<option value="">All Categories</option>
{% for category in event_categories %}
<option value="{{ category }}" {% if request.GET.event_category == category %}selected{% endif %}>
{{ category|title }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label for="date_from" class="form-label">From Date</label>
<input type="date" name="date_from" id="date_from" class="form-control"
value="{{ request.GET.date_from }}">
</div>
<div class="col-md-2">
<label for="date_to" class="form-label">To Date</label>
<input type="date" name="date_to" id="date_to" class="form-control"
value="{{ request.GET.date_to }}">
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-search me-1"></i>Filter
</button>
<a href="{% url 'core:audit_log' %}" class="btn btn-outline-secondary">
<i class="fas fa-times"></i>
</a>
</div>
</form>
</div>
</div>
<!-- Search and Results -->
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-list me-2"></i>Audit Entries
{% if object_list %}
<span class="badge bg-secondary ms-2">{{ object_list|length }}</span>
{% endif %}
</h5>
<div class="input-group" style="width: 300px;">
<input type="text"
class="form-control"
placeholder="Search audit logs..."
hx-get="{% url 'core:audit_log_search' %}"
hx-trigger="keyup changed delay:300ms"
hx-target="#audit-log-results"
hx-include="[name='event_type'], [name='event_category'], [name='date_from'], [name='date_to']">
<span class="input-group-text">
<i class="fas fa-search"></i>
</span>
</div>
</div>
</div>
<div class="card-body p-0">
<div id="audit-log-results">
{% include 'core/partials/audit_log_list.html' %}
</div>
</div>
<!-- Pagination -->
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div>
</div>
<script>
// Audit log functionality
document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh audit log every 30 seconds
setInterval(refreshAuditLog, 30000);
// Initialize date inputs with reasonable defaults
const dateFrom = document.getElementById('date_from');
const dateTo = document.getElementById('date_to');
if (!dateFrom.value) {
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
dateFrom.value = weekAgo.toISOString().split('T')[0];
}
if (!dateTo.value) {
const today = new Date();
dateTo.value = today.toISOString().split('T')[0];
}
});
function refreshAuditLog() {
const resultsContainer = document.getElementById('audit-log-results');
if (resultsContainer) {
// Show loading indicator
resultsContainer.innerHTML = `
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2 text-muted">Refreshing audit log...</p>
</div>
`;
// Trigger HTMX refresh
htmx.trigger(resultsContainer, 'refresh');
}
}
{#function exportAuditLog(format) {#}
{# const form = document.querySelector('form');#}
{# const formData = new FormData(form);#}
{# const params = new URLSearchParams(formData);#}
{# params.append('format', format);#}
{# #}
{# window.open(`{% url 'core:audit_log_export' %}?${params}`, '_blank');#}
{# }#}
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') {
return;
}
switch (e.key) {
case 'r':
e.preventDefault();
refreshAuditLog();
break;
case 'f':
e.preventDefault();
document.getElementById('event_type').focus();
break;
case '/':
e.preventDefault();
document.querySelector('input[placeholder*="Search"]').focus();
break;
}
});
// Real-time updates via HTMX
document.body.addEventListener('htmx:afterRequest', function(event) {
if (event.detail.target.id === 'audit-log-results') {
// Update timestamp
const timestamp = new Date().toLocaleTimeString();
console.log(`Audit log updated at ${timestamp}`);
}
});
</script>
<style>
.audit-log-entry {
border-left: 4px solid #dee2e6;
padding: 0.75rem;
margin-bottom: 0.5rem;
background: white;
border-radius: 0.375rem;
transition: all 0.2s ease;
}
.audit-log-entry:hover {
background: #f8f9fa;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.audit-log-entry.create { border-left-color: #28a745; }
.audit-log-entry.update { border-left-color: #ffc107; }
.audit-log-entry.delete { border-left-color: #dc3545; }
.audit-log-entry.view { border-left-color: #17a2b8; }
.audit-log-entry.error { border-left-color: #dc3545; background-color: #fff5f5; }
.audit-log-entry.warning { border-left-color: #ffc107; background-color: #fffbf0; }
.audit-meta {
font-size: 0.875rem;
color: #6c757d;
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.input-group {
max-width: 300px;
}
@media (max-width: 768px) {
.input-group {
max-width: 100%;
margin-top: 1rem;
}
.d-flex.justify-content-between {
flex-direction: column;
align-items: stretch !important;
}
}
</style>
{% endblock %}