267 lines
9.3 KiB
HTML
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 %}
|
|
|