Files
winauthmon-server/templates/auth/error_logs.html
2025-05-25 20:26:18 +01:00

406 lines
18 KiB
HTML

{% extends "base.html" %}
{% block head %}
<!-- DataTables CSS -->
<link href="{{ url_for('static', filename='css/dataTables.bootstrap5.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/buttons.bootstrap5.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/buttons.dataTables.min.css') }}" rel="stylesheet">
<style>
.log-level-CRITICAL { color: #dc3545; font-weight: bold; }
.log-level-ERROR { color: #dc3545; }
.log-level-WARNING { color: #ffc107; }
.log-level-INFO { color: #0dcaf0; }
.log-level-DEBUG { color: #6c757d; }
.log-message {
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.exception-toggle {
cursor: pointer;
color: #0d6efd;
text-decoration: underline;
}
.exception-details {
background-color: #343a40;
border: 1px solid #495057;
border-radius: 0.375rem;
padding: 1rem;
margin-top: 0.5rem;
font-family: monospace;
font-size: 0.875rem;
white-space: pre-wrap;
overflow-x: auto;
}
.filter-form {
background-color: #343a40;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.stats-cards {
margin-bottom: 1.5rem;
}
.stat-card {
background-color: #343a40;
border: 1px solid #495057;
border-radius: 0.375rem;
padding: 1rem;
text-align: center;
cursor: pointer;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.stat-label {
color: #adb5bd;
font-size: 0.875rem;
}
</style>
{% endblock %}
{% block title %}Error Logs{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Application Error Logs</h2>
<div>
<button type="button" class="btn btn-outline-danger btn-sm" data-bs-toggle="modal" data-bs-target="#clearLogsModal">
Clear Old Logs
</button>
<button id="refresh-logs" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-refresh"></i> Refresh
</button>
</div>
</div>
<!-- Statistics Cards -->
<div class="row stats-cards">
<div class="col-md">
<div class="stat-card">
<div class="stat-number text-info" id="debug-count">{{ error_logs | selectattr('level', 'equalto', 'DEBUG') | list | length }}</div>
<div class="stat-label">Debug</div>
</div>
</div>
<div class="col-md">
<div class="stat-card">
<div class="stat-number text-info" id="info-count">{{ error_logs | selectattr('level', 'equalto', 'INFO') | list | length }}</div>
<div class="stat-label">Info</div>
</div>
</div>
<div class="col-md">
<div class="stat-card">
<div class="stat-number text-danger" id="critical-count">{{ error_logs | selectattr('level', 'equalto', 'CRITICAL') | list | length }}</div>
<div class="stat-label">Critical</div>
</div>
</div>
<div class="col-md">
<div class="stat-card">
<div class="stat-number text-danger" id="error-count">{{ error_logs | selectattr('level', 'equalto', 'ERROR') | list | length }}</div>
<div class="stat-label">Errors</div>
</div>
</div>
<div class="col-md">
<div class="stat-card">
<div class="stat-number text-warning" id="warning-count">{{ error_logs | selectattr('level', 'equalto', 'WARNING') | list | length }}</div>
<div class="stat-label">Warnings</div>
</div>
</div>
<div class="col-md">
<div class="stat-card">
<div class="stat-number text-info" id="total-count">{{ error_logs | length }}</div>
<div class="stat-label">Total Logs</div>
</div>
</div>
</div>
<!-- Filter Form -->
<div class="filter-form">
<form method="GET" action="{{ url_for('auth.view_error_logs') }}">
<div class="row align-items-end">
<div class="col-md-3">
<label for="start_date" class="form-label">Start Date:</label>
<input type="date" class="form-control" id="start_date" name="start_date" value="{{ start_date }}">
</div>
<div class="col-md-3">
<label for="end_date" class="form-label">End Date:</label>
<input type="date" class="form-control" id="end_date" name="end_date" value="{{ end_date }}">
</div>
<div class="col-md-3">
<label for="level" class="form-label">Log Level:</label>
<select class="form-select" id="level" name="level">
<option value="">All Levels</option>
{% for level in available_levels %}
<option value="{{ level }}" {% if level == level_filter %}selected{% endif %}>{{ level }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary">Apply Filters</button>
<a href="{{ url_for('auth.view_error_logs') }}" class="btn btn-outline-secondary">Reset</a>
</div>
</div>
</form>
</div>
<!-- Error Logs Table -->
<div class="card">
<div class="card-body">
<table id="errorLogsTable" class="table table-striped table-bordered" style="width:100%">
<thead>
<tr>
<th>Timestamp</th>
<th>Level</th>
<th>Logger</th>
<th>Message</th>
<th>User</th>
<th>IP Address</th>
<th>Request ID</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for log in error_logs %}
<tr>
<td data-order="{{ log.timestamp.strftime('%Y%m%d%H%M%S') }}">
{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
</td>
<td>
<span class="log-level-{{ log.level }}">{{ log.level }}</span>
</td>
<td>{{ log.logger_name or 'N/A' }}</td>
<td>
<div class="log-message" title="{{ log.message }}">{{ log.message }}</div>
{% if log.exception %}
<small class="exception-toggle" onclick="toggleException({{ log.id }})">
View Exception
</small>
<div id="exception-{{ log.id }}" class="exception-details" style="display: none;">
{{ log.exception }}
</div>
{% endif %}
</td>
<td>{{ log.user_id or 'N/A' }}</td>
<td>{{ log.remote_addr or 'N/A' }}</td>
<td>{{ log.request_id or 'N/A' }}</td>
<td>
<button class="btn btn-sm btn-outline-info" onclick="viewLogDetail({{ log.id }})">
Details
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Clear Logs Modal -->
<div class="modal fade" id="clearLogsModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Clear Old Error Logs</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>This will permanently delete error logs older than the specified number of days.</p>
<div class="mb-3">
<label for="daysToKeep" class="form-label">Keep logs for (days):</label>
<input type="number" class="form-control" id="daysToKeep" value="30" min="1" max="365">
<div class="form-text">Logs older than this many days will be deleted.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" onclick="clearOldLogs()">Clear Logs</button>
</div>
</div>
</div>
</div>
<!-- Log Detail Modal -->
<div class="modal fade" id="logDetailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Error Log Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="logDetailContent">
<!-- Content loaded via JavaScript -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<!-- DataTables JS -->
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/dataTables.bootstrap5.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/dataTables.buttons.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/buttons.bootstrap5.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/buttons.html5.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/jszip.min.js') }}"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
var table = $('#errorLogsTable').DataTable({
pageLength: 25,
lengthMenu: [[25, 50, 100, 200, -1], [25, 50, 100, 200, "All"]],
order: [[0, 'desc']], // Sort by timestamp descending
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
buttons: [
{
extend: 'csv',
text: 'Export CSV',
className: 'btn btn-secondary btn-sm',
filename: 'error_logs_' + new Date().toISOString().split('T')[0]
}
],
language: {
search: "Search logs:",
lengthMenu: "Show _MENU_ logs per page",
info: "Showing _START_ to _END_ of _TOTAL_ logs",
paginate: {
first: "First",
last: "Last",
next: "Next",
previous: "Previous"
}
}
});
// Stats card click events
$('#debug-count').closest('.stat-card').on('click', function() {
table.column(1).search('Debug').draw();
});
$('#info-count').closest('.stat-card').on('click', function() {
table.column(1).search('Info').draw();
});
$('#critical-count').closest('.stat-card').on('click', function() {
table.column(1).search('CRITICAL').draw();
});
$('#error-count').closest('.stat-card').on('click', function() {
table.column(1).search('ERROR').draw();
});
$('#warning-count').closest('.stat-card').on('click', function() {
table.column(1).search('WARNING').draw();
});
$('#total-count').closest('.stat-card').on('click', function() {
table.column(1).search('').draw();
});
// Refresh button
$('#refresh-logs').on('click', function() {
location.reload();
});
});
function toggleException(logId) {
var element = document.getElementById('exception-' + logId);
if (element.style.display === 'none') {
element.style.display = 'block';
} else {
element.style.display = 'none';
}
}
function viewLogDetail(logId) {
fetch(`{{ url_for('auth.view_error_log_detail', log_id=0) }}`.replace('0', logId))
.then(response => response.json())
.then(data => {
var content = `
<div class="row">
<div class="col-md-6"><strong>ID:</strong> ${data.id}</div>
<div class="col-md-6"><strong>Level:</strong> <span class="log-level-${data.level}">${data.level}</span></div>
</div>
<div class="row mt-2">
<div class="col-md-6"><strong>Logger:</strong> ${data.logger_name || 'N/A'}</div>
<div class="col-md-6"><strong>Timestamp:</strong> ${new Date(data.timestamp).toLocaleString()}</div>
</div>
<div class="row mt-2">
<div class="col-md-6"><strong>User ID:</strong> ${data.user_id || 'N/A'}</div>
<div class="col-md-6"><strong>IP Address:</strong> ${data.remote_addr || 'N/A'}</div>
</div>
<div class="row mt-2">
<div class="col-md-6"><strong>Request ID:</strong> ${data.request_id || 'N/A'}</div>
<div class="col-md-6"><strong>File:</strong> ${data.pathname || 'N/A'}${data.lineno ? ':' + data.lineno : ''}</div>
</div>
<div class="mt-3">
<strong>Message:</strong>
<div class="exception-details mt-1">${data.message}</div>
</div>
${data.exception ? `
<div class="mt-3">
<strong>Exception:</strong>
<div class="exception-details mt-1">${data.exception}</div>
</div>
` : ''}
`;
document.getElementById('logDetailContent').innerHTML = content;
new bootstrap.Modal(document.getElementById('logDetailModal')).show();
})
.catch(error => {
alert('Error loading log details: ' + error);
});
}
function clearOldLogs() {
var daysToKeep = document.getElementById('daysToKeep').value;
if (!confirm(`Are you sure you want to delete all error logs older than ${daysToKeep} days? This action cannot be undone.`)) {
return;
}
fetch(`{{ url_for('auth.clear_error_logs') }}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': $('meta[name=csrf-token]').attr('content')
},
body: JSON.stringify({
days_to_keep: parseInt(daysToKeep)
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
location.reload();
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
alert('Error clearing logs: ' + error);
});
// Close modal
bootstrap.Modal.getInstance(document.getElementById('clearLogsModal')).hide();
}
</script>
{% endblock %}