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

380 lines
20 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Audit Logs{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<div class="container">
<ul class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item active">Audit Logs</li>
</ul>
<div class="row align-items-center mb-3">
<div class="col">
<h1 class="page-header">Audit Logs</h1>
<p class="text-muted">Track system activities and user actions for compliance and security</p>
</div>
<div class="col-auto">
<div class="btn-group">
<button type="button" class="btn btn-outline-primary" onclick="exportLogs()">
<i class="fa fa-download me-2"></i>Export
</button>
<button type="button" class="btn btn-outline-secondary" onclick="archiveLogs()">
<i class="fa fa-archive me-2"></i>Archive Old Logs
</button>
</div>
</div>
</div>
<!-- Log Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-primary bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-list fa-2x text-primary"></i>
</div>
<h5>Total Logs</h5>
<div class="fs-24px fw-600 text-primary">{{ total_logs }}</div>
<div class="text-muted small">All time</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-info bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-clock fa-2x text-info"></i>
</div>
<h5>Today's Logs</h5>
<div class="fs-24px fw-600 text-info">{{ today_logs }}</div>
<div class="text-muted small">Last 24 hours</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-warning bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-exclamation-triangle fa-2x text-warning"></i>
</div>
<h5>Security Events</h5>
<div class="fs-24px fw-600 text-warning">{{ security_logs }}</div>
<div class="text-muted small">This week</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-danger bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-times-circle fa-2x text-danger"></i>
</div>
<h5>Failed Actions</h5>
<div class="fs-24px fw-600 text-danger">{{ failed_logs }}</div>
<div class="text-muted small">This week</div>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3 align-items-end">
<div class="col-md-2">
<label class="form-label">Date From</label>
<input type="date" name="date_from" class="form-control" value="{{ request.GET.date_from }}">
</div>
<div class="col-md-2">
<label class="form-label">Date To</label>
<input type="date" name="date_to" class="form-control" value="{{ request.GET.date_to }}">
</div>
<div class="col-md-2">
<label class="form-label">User</label>
<select name="user" class="form-select">
<option value="">All Users</option>
{% for user in users %}
<option value="{{ user.id }}" {% if request.GET.user == user.id|stringformat:"s" %}selected{% endif %}>{{ user.get_full_name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label">Action Type</label>
<select name="action_type" class="form-select">
<option value="">All Actions</option>
<option value="create" {% if request.GET.action_type == 'create' %}selected{% endif %}>Create</option>
<option value="read" {% if request.GET.action_type == 'read' %}selected{% endif %}>Read</option>
<option value="update" {% if request.GET.action_type == 'update' %}selected{% endif %}>Update</option>
<option value="delete" {% if request.GET.action_type == 'delete' %}selected{% endif %}>Delete</option>
<option value="login" {% if request.GET.action_type == 'login' %}selected{% endif %}>Login</option>
<option value="logout" {% if request.GET.action_type == 'logout' %}selected{% endif %}>Logout</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Module</label>
<select name="module" class="form-select">
<option value="">All Modules</option>
<option value="patients" {% if request.GET.module == 'patients' %}selected{% endif %}>Patients</option>
<option value="appointments" {% if request.GET.module == 'appointments' %}selected{% endif %}>Appointments</option>
<option value="billing" {% if request.GET.module == 'billing' %}selected{% endif %}>Billing</option>
<option value="pharmacy" {% if request.GET.module == 'pharmacy' %}selected{% endif %}>Pharmacy</option>
<option value="laboratory" {% if request.GET.module == 'laboratory' %}selected{% endif %}>Laboratory</option>
<option value="radiology" {% if request.GET.module == 'radiology' %}selected{% endif %}>Radiology</option>
<option value="accounts" {% if request.GET.module == 'accounts' %}selected{% endif %}>User Accounts</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">
<i class="fa fa-search me-2"></i>Filter
</button>
<a href="{% url 'core:audit_logs' %}" class="btn btn-outline-secondary ms-2">
<i class="fa fa-times me-2"></i>Clear
</a>
</div>
</form>
</div>
</div>
<!-- Audit Logs Table -->
<div class="card">
<div class="card-header">
<h4 class="card-title">System Audit Trail</h4>
<div class="card-toolbar">
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" onclick="refreshLogs()">
<i class="fa fa-refresh"></i>
</button>
<button type="button" class="btn btn-outline-secondary" onclick="toggleAutoRefresh()">
<i class="fa fa-play" id="autoRefreshIcon"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped" id="auditLogsTable">
<thead>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Module</th>
<th>Object</th>
<th>IP Address</th>
<th>Status</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>
<div>{{ log.timestamp|date:"M d, Y" }}</div>
<div class="text-muted small">{{ log.timestamp|date:"g:i:s A" }}</div>
</td>
<td>
<div class="d-flex align-items-center">
<div class="w-30px h-30px bg-primary bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle me-2">
<i class="fa fa-user text-primary fa-sm"></i>
</div>
<div>
<div class="fw-bold">{{ log.user.get_full_name|default:log.user.username }}</div>
<div class="text-muted small">{{ log.user.email }}</div>
</div>
</div>
</td>
<td>
<span class="badge bg-{{ log.action_color }}">
<i class="fa fa-{{ log.action_icon }} me-1"></i>{{ log.get_action_display }}
</span>
</td>
<td>
<span class="badge bg-info">{{ log.module|title }}</span>
</td>
<td>
{% if log.object_repr %}
<div class="fw-bold">{{ log.object_repr|truncatechars:30 }}</div>
<div class="text-muted small">{{ log.content_type.model|title }} #{{ log.object_id }}</div>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
<td>
<div>{{ log.ip_address }}</div>
{% if log.user_agent %}
<div class="text-muted small" title="{{ log.user_agent }}">
{{ log.browser_info|default:"Unknown browser" }}
</div>
{% endif %}
</td>
<td>
{% if log.success %}
<span class="badge bg-success">
<i class="fa fa-check me-1"></i>Success
</span>
{% else %}
<span class="badge bg-danger">
<i class="fa fa-times me-1"></i>Failed
</span>
{% endif %}
</td>
<td>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="viewLogDetails('{{ log.id }}')">
<i class="fa fa-eye"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Audit logs pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.user %}&user={{ request.GET.user }}{% endif %}{% if request.GET.action_type %}&action_type={{ request.GET.action_type }}{% endif %}{% if request.GET.module %}&module={{ request.GET.module }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.user %}&user={{ request.GET.user }}{% endif %}{% if request.GET.action_type %}&action_type={{ request.GET.action_type }}{% endif %}{% if request.GET.module %}&module={{ request.GET.module }}{% endif %}">Previous</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.user %}&user={{ request.GET.user }}{% endif %}{% if request.GET.action_type %}&action_type={{ request.GET.action_type }}{% endif %}{% if request.GET.module %}&module={{ request.GET.module }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.user %}&user={{ request.GET.user }}{% endif %}{% if request.GET.action_type %}&action_type={{ request.GET.action_type }}{% endif %}{% if request.GET.module %}&module={{ request.GET.module }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.user %}&user={{ request.GET.user }}{% endif %}{% if request.GET.action_type %}&action_type={{ request.GET.action_type }}{% endif %}{% if request.GET.module %}&module={{ request.GET.module }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Log Details Modal -->
<div class="modal fade" id="logDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Audit Log Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="logDetailsContent">
<!-- Content loaded via AJAX -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
var autoRefreshInterval;
var autoRefreshEnabled = false;
$(document).ready(function() {
// Initialize DataTable
$('#auditLogsTable').DataTable({
responsive: true,
pageLength: 25,
order: [[0, 'desc']],
columnDefs: [
{ orderable: false, targets: [7] }
]
});
// Initialize Select2
$('.form-select').select2({
minimumResultsForSearch: 10
});
});
function viewLogDetails(logId) {
$.get('{% url "core:audit_log_detail" 0 %}'.replace('0', logId), function(data) {
$('#logDetailsContent').html(data);
$('#logDetailsModal').modal('show');
}).fail(function() {
toastr.error('Failed to load log details');
});
}
function refreshLogs() {
location.reload();
}
function toggleAutoRefresh() {
if (autoRefreshEnabled) {
clearInterval(autoRefreshInterval);
autoRefreshEnabled = false;
$('#autoRefreshIcon').removeClass('fa-pause').addClass('fa-play');
toastr.info('Auto-refresh disabled');
} else {
autoRefreshInterval = setInterval(function() {
refreshLogs();
}, 30000); // Refresh every 30 seconds
autoRefreshEnabled = true;
$('#autoRefreshIcon').removeClass('fa-play').addClass('fa-pause');
toastr.info('Auto-refresh enabled (30s interval)');
}
}
function exportLogs() {
var params = new URLSearchParams(window.location.search);
params.set('export', 'csv');
window.location.href = '{% url "core:audit_logs" %}?' + params.toString();
}
function archiveLogs() {
if (confirm('Archive logs older than 90 days? This will move them to long-term storage.')) {
$.post('{% url "core:archive_audit_logs" %}', {
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success('Logs archived successfully');
location.reload();
} else {
toastr.error('Failed to archive logs: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to archive logs');
});
}
}
</script>
{% endblock %}