1020 lines
23 KiB
HTML
1020 lines
23 KiB
HTML
<!-- templates/admin/logs.html -->
|
|
{% extends "base_authenticated.html" %}
|
|
{% block title %}System Logs - QR Code Management{% endblock %}
|
|
|
|
{% block extra_head %}
|
|
<!-- Admin Logs CSS -->
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin-logs.css') }}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="logs-page">
|
|
<!-- Page Header -->
|
|
<div class="logs-header">
|
|
<div class="header-content">
|
|
<div class="header-navigation">
|
|
<a href="{{ url_for('dashboard') }}" class="back-button">
|
|
<i class="fas fa-arrow-left"></i>
|
|
<span>Back to Dashboard</span>
|
|
</a>
|
|
</div>
|
|
<h1><i class="fas fa-clipboard-list"></i> System Logs</h1>
|
|
<p>Monitor system activities and security events</p>
|
|
</div>
|
|
<div class="header-actions">
|
|
<button class="btn btn-secondary" onclick="refreshLogs()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
Refresh
|
|
</button>
|
|
<button class="btn btn-warning" onclick="cleanupLogs()">
|
|
<i class="fas fa-trash-alt"></i>
|
|
Cleanup Old Logs
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Statistics -->
|
|
{% if log_stats %}
|
|
<div class="log-stats">
|
|
<div class="stat-card total">
|
|
<div class="stat-icon">
|
|
<i class="fas fa-list"></i>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3>{{ log_stats.total_events or 0 }}</h3>
|
|
<p>Total Events (7 days)</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card security">
|
|
<div class="stat-icon security">
|
|
<i class="fas fa-shield-alt"></i>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3>{{ log_stats.security_events or 0 }}</h3>
|
|
<p>Security Events</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card errors">
|
|
<div class="stat-icon errors">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3>{{ log_stats.database_errors or 0 }}</h3>
|
|
<p>Database Errors</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card users">
|
|
<div class="stat-icon users">
|
|
<i class="fas fa-users"></i>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3>{{ log_stats.user_activities or 0 }}</h3>
|
|
<p>User Activities</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Log Controls -->
|
|
<div class="log-controls">
|
|
<div class="search-filters">
|
|
<div class="search-box">
|
|
<i class="fas fa-search"></i>
|
|
<input
|
|
type="text"
|
|
id="searchLogs"
|
|
placeholder="Search logs by event type, description, or username..."
|
|
/>
|
|
</div>
|
|
|
|
<div class="filter-group">
|
|
<select id="categoryFilter" class="filter-select">
|
|
<option value="">All Categories</option>
|
|
<option value="security">Security Events</option>
|
|
<option value="database">Database Events</option>
|
|
<option value="user_activity">User Activities</option>
|
|
<option value="system">System Events</option>
|
|
</select>
|
|
|
|
<select id="severityFilter" class="filter-select">
|
|
<option value="">All Severity</option>
|
|
<option value="HIGH">High</option>
|
|
<option value="MEDIUM">Medium</option>
|
|
<option value="LOW">Low</option>
|
|
<option value="INFO">Info</option>
|
|
</select>
|
|
|
|
<select id="daysFilter" class="filter-select">
|
|
<option value="1">Last 24 Hours</option>
|
|
<option value="7" selected>Last 7 Days</option>
|
|
<option value="30">Last 30 Days</option>
|
|
<option value="90">Last 90 Days</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="view-options">
|
|
<button class="btn btn-secondary btn-sm" onclick="exportLogs()">
|
|
<i class="fas fa-download"></i>
|
|
Export
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logs Table -->
|
|
<div class="logs-table-container">
|
|
<div id="logsTableWrapper">
|
|
<div class="loading-state" id="loadingState">
|
|
<i class="fas fa-spinner fa-spin"></i>
|
|
<p>Loading log entries...</p>
|
|
</div>
|
|
|
|
<div class="empty-state" id="emptyState" style="display: none;">
|
|
<i class="fas fa-clipboard-list"></i>
|
|
<h3>No Log Entries Found</h3>
|
|
<p>No log entries match your current filter criteria.</p>
|
|
</div>
|
|
|
|
<table class="logs-table" id="logsTable" style="display: none;">
|
|
<thead>
|
|
<tr>
|
|
<th>Timestamp</th>
|
|
<th>Event Type</th>
|
|
<th>Category</th>
|
|
<th>Description</th>
|
|
<th>Severity</th>
|
|
<th>User</th>
|
|
<th>IP Address</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="logsTableBody">
|
|
<!-- Logs will be loaded via JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="pagination-wrapper" id="paginationWrapper" style="display: none;">
|
|
<div class="pagination-info">
|
|
<span id="paginationInfo">Showing 0 of 0 entries</span>
|
|
</div>
|
|
<div class="pagination-controls">
|
|
<button class="btn btn-sm" id="prevPage" onclick="previousPage()">
|
|
<i class="fas fa-chevron-left"></i>
|
|
Previous
|
|
</button>
|
|
<span class="pagination-numbers" id="pageNumbers"></span>
|
|
<button class="btn btn-sm" id="nextPage" onclick="nextPage()">
|
|
Next
|
|
<i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cleanup Modal -->
|
|
<div class="modal" id="cleanupModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3><i class="fas fa-trash-alt"></i> Cleanup Old Logs</h3>
|
|
<button class="modal-close" onclick="closeCleanupModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>This action will permanently delete log entries older than the specified number of days.</p>
|
|
<div class="form-group">
|
|
<label for="daysToKeep">Keep logs for the last:</label>
|
|
<select id="daysToKeep" class="form-control">
|
|
<option value="30">30 days</option>
|
|
<option value="60">60 days</option>
|
|
<option value="90" selected>90 days</option>
|
|
<option value="180">180 days</option>
|
|
<option value="365">1 year</option>
|
|
</select>
|
|
</div>
|
|
<div class="warning-note">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Warning:</strong> This action cannot be undone. Deleted log entries will be permanently removed from the system.
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeCleanupModal()">Cancel</button>
|
|
<button class="btn btn-danger" onclick="confirmCleanup()">
|
|
<i class="fas fa-trash-alt"></i>
|
|
Delete Old Logs
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Global variables
|
|
let currentPage = 1;
|
|
let logsPerPage = 50;
|
|
let totalLogs = 0;
|
|
let currentFilters = {
|
|
search: '',
|
|
category: '',
|
|
severity: '',
|
|
days: 7
|
|
};
|
|
|
|
// Initialize page
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadLogs();
|
|
setupEventListeners();
|
|
});
|
|
|
|
// Setup event listeners
|
|
function setupEventListeners() {
|
|
// Search input
|
|
const searchInput = document.getElementById('searchLogs');
|
|
let searchTimeout;
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
currentFilters.search = this.value;
|
|
currentPage = 1;
|
|
loadLogs();
|
|
}, 500);
|
|
});
|
|
|
|
// Filter selects
|
|
document.getElementById('categoryFilter').addEventListener('change', function() {
|
|
currentFilters.category = this.value;
|
|
currentPage = 1;
|
|
loadLogs();
|
|
});
|
|
|
|
document.getElementById('severityFilter').addEventListener('change', function() {
|
|
currentFilters.severity = this.value;
|
|
currentPage = 1;
|
|
loadLogs();
|
|
});
|
|
|
|
document.getElementById('daysFilter').addEventListener('change', function() {
|
|
currentFilters.days = parseInt(this.value);
|
|
currentPage = 1;
|
|
loadLogs();
|
|
loadStats();
|
|
});
|
|
}
|
|
|
|
// Load logs from API
|
|
async function loadLogs() {
|
|
showLoading();
|
|
|
|
try {
|
|
const params = new URLSearchParams({
|
|
days: currentFilters.days,
|
|
limit: logsPerPage,
|
|
page: currentPage
|
|
});
|
|
|
|
const response = await fetch(`/api/logs/recent?${params}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
displayLogs(data.logs);
|
|
totalLogs = data.total || data.logs.length;
|
|
updatePagination();
|
|
} else {
|
|
showError('Failed to load logs: ' + (data.error || 'Unknown error'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading logs:', error);
|
|
showError('Failed to load logs. Please try again.');
|
|
}
|
|
}
|
|
|
|
// Load log statistics
|
|
async function loadStats() {
|
|
try {
|
|
const response = await fetch(`/api/logs/stats?days=${currentFilters.days}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.stats) {
|
|
updateStatsDisplay(data.stats);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading stats:', error);
|
|
}
|
|
}
|
|
|
|
// Display logs in table
|
|
function displayLogs(logs) {
|
|
const tbody = document.getElementById('logsTableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
if (logs.length === 0) {
|
|
showEmpty();
|
|
return;
|
|
}
|
|
|
|
// Filter logs based on current filters
|
|
let filteredLogs = logs.filter(log => {
|
|
if (currentFilters.search) {
|
|
const searchLower = currentFilters.search.toLowerCase();
|
|
if (!log.event_type.toLowerCase().includes(searchLower) &&
|
|
!log.description.toLowerCase().includes(searchLower) &&
|
|
!(log.username && log.username.toLowerCase().includes(searchLower))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (currentFilters.category && log.event_category !== currentFilters.category) {
|
|
return false;
|
|
}
|
|
|
|
if (currentFilters.severity && log.severity !== currentFilters.severity) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
filteredLogs.forEach(log => {
|
|
const row = document.createElement('tr');
|
|
row.className = getSeverityClass(log.severity);
|
|
|
|
const timestamp = new Date(log.timestamp).toLocaleString();
|
|
|
|
row.innerHTML = `
|
|
<td class="timestamp">${timestamp}</td>
|
|
<td class="event-type">${escapeHtml(log.event_type)}</td>
|
|
<td class="category">
|
|
<span class="category-badge ${log.event_category}">${log.event_category}</span>
|
|
</td>
|
|
<td class="description">${escapeHtml(log.description)}</td>
|
|
<td class="severity">
|
|
<span class="severity-badge ${log.severity.toLowerCase()}">${log.severity}</span>
|
|
</td>
|
|
<td class="username">${log.username ? escapeHtml(log.username) : '<em>System</em>'}</td>
|
|
<td class="ip-address">${log.ip_address || '-'}</td>
|
|
`;
|
|
|
|
tbody.appendChild(row);
|
|
});
|
|
|
|
showTable();
|
|
}
|
|
|
|
// Utility functions
|
|
function getSeverityClass(severity) {
|
|
switch (severity) {
|
|
case 'HIGH': return 'severity-high';
|
|
case 'MEDIUM': return 'severity-medium';
|
|
case 'LOW': return 'severity-low';
|
|
default: return 'severity-info';
|
|
}
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
if (!text) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function showLoading() {
|
|
document.getElementById('loadingState').style.display = 'block';
|
|
document.getElementById('emptyState').style.display = 'none';
|
|
document.getElementById('logsTable').style.display = 'none';
|
|
document.getElementById('paginationWrapper').style.display = 'none';
|
|
}
|
|
|
|
function showEmpty() {
|
|
document.getElementById('loadingState').style.display = 'none';
|
|
document.getElementById('emptyState').style.display = 'block';
|
|
document.getElementById('logsTable').style.display = 'none';
|
|
document.getElementById('paginationWrapper').style.display = 'none';
|
|
}
|
|
|
|
function showTable() {
|
|
document.getElementById('loadingState').style.display = 'none';
|
|
document.getElementById('emptyState').style.display = 'none';
|
|
document.getElementById('logsTable').style.display = 'table';
|
|
document.getElementById('paginationWrapper').style.display = 'flex';
|
|
}
|
|
|
|
function showError(message) {
|
|
// Simple error display - you could enhance this with a proper toast/notification system
|
|
alert(message);
|
|
showEmpty();
|
|
}
|
|
|
|
// Update pagination
|
|
function updatePagination() {
|
|
const totalPages = Math.ceil(totalLogs / logsPerPage);
|
|
const info = document.getElementById('paginationInfo');
|
|
|
|
const start = (currentPage - 1) * logsPerPage + 1;
|
|
const end = Math.min(currentPage * logsPerPage, totalLogs);
|
|
|
|
info.textContent = `Showing ${start}-${end} of ${totalLogs} entries`;
|
|
|
|
document.getElementById('prevPage').disabled = currentPage <= 1;
|
|
document.getElementById('nextPage').disabled = currentPage >= totalPages;
|
|
}
|
|
|
|
// Pagination controls
|
|
function previousPage() {
|
|
if (currentPage > 1) {
|
|
currentPage--;
|
|
loadLogs();
|
|
}
|
|
}
|
|
|
|
function nextPage() {
|
|
currentPage++;
|
|
loadLogs();
|
|
}
|
|
|
|
// Refresh logs
|
|
function refreshLogs() {
|
|
loadLogs();
|
|
loadStats();
|
|
}
|
|
|
|
// Export logs
|
|
function exportLogs() {
|
|
const params = new URLSearchParams({
|
|
days: currentFilters.days,
|
|
format: 'csv'
|
|
});
|
|
|
|
window.open(`/api/logs/export?${params}`, '_blank');
|
|
}
|
|
|
|
// Cleanup modal functions
|
|
function cleanupLogs() {
|
|
document.getElementById('cleanupModal').style.display = 'flex';
|
|
}
|
|
|
|
function closeCleanupModal() {
|
|
document.getElementById('cleanupModal').style.display = 'none';
|
|
}
|
|
|
|
async function confirmCleanup() {
|
|
const daysToKeep = parseInt(document.getElementById('daysToKeep').value);
|
|
|
|
try {
|
|
const response = await fetch('/api/logs/cleanup', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ days_to_keep: daysToKeep })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
alert(`Successfully cleaned up ${data.deleted_count} old log entries.`);
|
|
closeCleanupModal();
|
|
loadLogs();
|
|
loadStats();
|
|
} else {
|
|
alert('Error cleaning up logs: ' + (data.error || 'Unknown error'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error cleaning up logs:', error);
|
|
alert('Failed to cleanup logs. Please try again.');
|
|
}
|
|
}
|
|
|
|
function updateStatsDisplay(stats) {
|
|
// Update stat cards if they exist
|
|
const totalElement = document.querySelector('.stat-card.total h3');
|
|
const securityElement = document.querySelector('.stat-card.security h3');
|
|
const errorsElement = document.querySelector('.stat-card.errors h3');
|
|
const usersElement = document.querySelector('.stat-card.users h3');
|
|
|
|
if (totalElement) totalElement.textContent = stats.total_events || 0;
|
|
if (securityElement) securityElement.textContent = stats.security_events || 0;
|
|
if (errorsElement) errorsElement.textContent = stats.database_errors || 0;
|
|
if (usersElement) usersElement.textContent = stats.user_activities || 0;
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
/* Admin Logs Styles */
|
|
.logs-page {
|
|
padding: var(--spacing-6);
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.logs-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: var(--spacing-8);
|
|
padding-bottom: var(--spacing-6);
|
|
border-bottom: 2px solid var(--gray-200);
|
|
}
|
|
|
|
.header-content h1 {
|
|
font-size: var(--font-size-3xl);
|
|
font-weight: 700;
|
|
color: var(--gray-900);
|
|
margin-bottom: var(--spacing-2);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-3);
|
|
}
|
|
|
|
.header-content p {
|
|
color: var(--gray-600);
|
|
font-size: var(--font-size-lg);
|
|
}
|
|
|
|
.back-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--spacing-2);
|
|
color: var(--primary-color);
|
|
text-decoration: none;
|
|
font-size: var(--font-size-sm);
|
|
margin-bottom: var(--spacing-4);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.back-button:hover {
|
|
color: var(--primary-hover);
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
gap: var(--spacing-3);
|
|
}
|
|
|
|
/* Log Statistics */
|
|
.log-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: var(--spacing-6);
|
|
margin-bottom: var(--spacing-8);
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--white);
|
|
padding: var(--spacing-6);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-4);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: var(--radius-lg);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: var(--font-size-xl);
|
|
color: var(--white);
|
|
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
|
|
}
|
|
|
|
.stat-icon.security {
|
|
background: linear-gradient(135deg, var(--warning-color), #b45309);
|
|
}
|
|
|
|
.stat-icon.errors {
|
|
background: linear-gradient(135deg, var(--danger-color), #b91c1c);
|
|
}
|
|
|
|
.stat-icon.users {
|
|
background: linear-gradient(135deg, var(--success-color), #059669);
|
|
}
|
|
|
|
.stat-info h3 {
|
|
font-size: var(--font-size-2xl);
|
|
font-weight: 700;
|
|
color: var(--gray-900);
|
|
margin-bottom: var(--spacing-1);
|
|
}
|
|
|
|
.stat-info p {
|
|
color: var(--gray-500);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
/* Log Controls */
|
|
.log-controls {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--spacing-6);
|
|
padding: var(--spacing-4);
|
|
background: var(--white);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.search-filters {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-4);
|
|
flex: 1;
|
|
}
|
|
|
|
.search-box {
|
|
position: relative;
|
|
flex: 1;
|
|
max-width: 400px;
|
|
}
|
|
|
|
.search-box input {
|
|
width: 100%;
|
|
padding: var(--spacing-3) var(--spacing-3) var(--spacing-3) var(--spacing-10);
|
|
border: 1px solid var(--gray-300);
|
|
border-radius: var(--radius);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.search-box i {
|
|
position: absolute;
|
|
left: var(--spacing-3);
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--gray-400);
|
|
}
|
|
|
|
.filter-group {
|
|
display: flex;
|
|
gap: var(--spacing-3);
|
|
}
|
|
|
|
.filter-select {
|
|
padding: var(--spacing-2) var(--spacing-3);
|
|
border: 1px solid var(--gray-300);
|
|
border-radius: var(--radius);
|
|
font-size: var(--font-size-sm);
|
|
background: var(--white);
|
|
}
|
|
|
|
/* Logs Table */
|
|
.logs-table-container {
|
|
background: var(--white);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
overflow: hidden;
|
|
margin-bottom: var(--spacing-6);
|
|
}
|
|
|
|
.logs-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.logs-table th,
|
|
.logs-table td {
|
|
padding: var(--spacing-3) var(--spacing-4);
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.logs-table th {
|
|
background: var(--gray-50);
|
|
font-weight: 600;
|
|
color: var(--gray-700);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.logs-table tr:hover {
|
|
background: var(--gray-50);
|
|
}
|
|
|
|
.logs-table tr.severity-high {
|
|
border-left: 4px solid var(--danger-color);
|
|
}
|
|
|
|
.logs-table tr.severity-medium {
|
|
border-left: 4px solid var(--warning-color);
|
|
}
|
|
|
|
.logs-table tr.severity-low {
|
|
border-left: 4px solid var(--success-color);
|
|
}
|
|
|
|
.logs-table tr.severity-info {
|
|
border-left: 4px solid var(--primary-color);
|
|
}
|
|
|
|
.timestamp {
|
|
font-family: var(--font-mono);
|
|
font-size: var(--font-size-xs);
|
|
color: var(--gray-600);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.event-type {
|
|
font-weight: 600;
|
|
color: var(--gray-900);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.category-badge,
|
|
.severity-badge {
|
|
padding: var(--spacing-1) var(--spacing-2);
|
|
border-radius: var(--radius);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.category-badge.security {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.category-badge.database {
|
|
background: #dbeafe;
|
|
color: #1e40af;
|
|
}
|
|
|
|
.category-badge.user_activity {
|
|
background: #d1fae5;
|
|
color: #065f46;
|
|
}
|
|
|
|
.category-badge.system {
|
|
background: #f3e8ff;
|
|
color: #7c3aed;
|
|
}
|
|
|
|
.severity-badge.high {
|
|
background: var(--danger-color);
|
|
color: var(--white);
|
|
}
|
|
|
|
.severity-badge.medium {
|
|
background: var(--warning-color);
|
|
color: var(--white);
|
|
}
|
|
|
|
.severity-badge.low {
|
|
background: var(--success-color);
|
|
color: var(--white);
|
|
}
|
|
|
|
.severity-badge.info {
|
|
background: var(--primary-color);
|
|
color: var(--white);
|
|
}
|
|
|
|
.description {
|
|
max-width: 300px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.username {
|
|
font-weight: 500;
|
|
color: var(--gray-700);
|
|
}
|
|
|
|
.ip-address {
|
|
font-family: var(--font-mono);
|
|
font-size: var(--font-size-xs);
|
|
color: var(--gray-500);
|
|
}
|
|
|
|
/* Loading and Empty States */
|
|
.loading-state,
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: var(--spacing-12);
|
|
color: var(--gray-500);
|
|
}
|
|
|
|
.loading-state i {
|
|
font-size: var(--font-size-2xl);
|
|
margin-bottom: var(--spacing-4);
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.empty-state i {
|
|
font-size: 4rem;
|
|
margin-bottom: var(--spacing-4);
|
|
color: var(--gray-300);
|
|
}
|
|
|
|
.empty-state h3 {
|
|
font-size: var(--font-size-xl);
|
|
color: var(--gray-700);
|
|
margin-bottom: var(--spacing-2);
|
|
}
|
|
|
|
/* Pagination */
|
|
.pagination-wrapper {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-4);
|
|
background: var(--white);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.pagination-info {
|
|
color: var(--gray-600);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.pagination-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-2);
|
|
}
|
|
|
|
.pagination-controls button {
|
|
min-width: 80px;
|
|
}
|
|
|
|
.pagination-controls button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Modal Styles */
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 1000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.modal-content {
|
|
background: var(--white);
|
|
border-radius: var(--radius-lg);
|
|
max-width: 500px;
|
|
width: 90%;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-6);
|
|
border-bottom: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.modal-header h3 {
|
|
margin: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-2);
|
|
}
|
|
|
|
.modal-close {
|
|
background: none;
|
|
border: none;
|
|
font-size: var(--font-size-xl);
|
|
cursor: pointer;
|
|
color: var(--gray-400);
|
|
padding: var(--spacing-1);
|
|
}
|
|
|
|
.modal-close:hover {
|
|
color: var(--gray-600);
|
|
}
|
|
|
|
.modal-body {
|
|
padding: var(--spacing-6);
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: var(--spacing-3);
|
|
padding: var(--spacing-6);
|
|
border-top: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.warning-note {
|
|
background: #fef3c7;
|
|
border: 1px solid #f59e0b;
|
|
border-radius: var(--radius);
|
|
padding: var(--spacing-4);
|
|
margin-top: var(--spacing-4);
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--spacing-3);
|
|
}
|
|
|
|
.warning-note i {
|
|
color: #f59e0b;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* Responsive Design */
|
|
@media (max-width: 768px) {
|
|
.logs-page {
|
|
padding: var(--spacing-4);
|
|
}
|
|
|
|
.logs-header {
|
|
flex-direction: column;
|
|
gap: var(--spacing-4);
|
|
}
|
|
|
|
.header-actions {
|
|
width: 100%;
|
|
justify-content: stretch;
|
|
}
|
|
|
|
.header-actions .btn {
|
|
flex: 1;
|
|
}
|
|
|
|
.log-stats {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.log-controls {
|
|
flex-direction: column;
|
|
gap: var(--spacing-4);
|
|
}
|
|
|
|
.search-filters {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.filter-group {
|
|
width: 100%;
|
|
}
|
|
|
|
.filter-select {
|
|
width: 100%;
|
|
}
|
|
|
|
.logs-table-container {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.logs-table {
|
|
min-width: 800px;
|
|
}
|
|
|
|
.pagination-wrapper {
|
|
flex-direction: column;
|
|
gap: var(--spacing-3);
|
|
}
|
|
|
|
.pagination-controls {
|
|
width: 100%;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.stat-card {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: var(--spacing-3);
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 50px;
|
|
height: 50px;
|
|
}
|
|
|
|
.modal-content {
|
|
width: 95%;
|
|
margin: var(--spacing-4);
|
|
}
|
|
|
|
.modal-footer {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.modal-footer .btn {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %} |