284 lines
11 KiB
HTML
284 lines
11 KiB
HTML
{% macro logs_tab() %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<!-- Filters -->
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<form id="logFiltersForm" class="row g-3">
|
|
<div class="col-md-3">
|
|
<label for="logLevel" class="form-label">Log Level</label>
|
|
<select class="form-select" id="logLevel" name="level">
|
|
<option value="">All Levels</option>
|
|
<option value="INFO">Info</option>
|
|
<option value="WARNING">Warning</option>
|
|
<option value="ERROR">Error</option>
|
|
<option value="DEBUG">Debug</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label for="logCategory" class="form-label">Category</label>
|
|
<select class="form-select" id="logCategory" name="category">
|
|
<option value="">All Categories</option>
|
|
<option value="AUTH">Authentication</option>
|
|
<option value="FILE">File Operations</option>
|
|
<option value="USER">User Management</option>
|
|
<option value="SYSTEM">System</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label for="startDate" class="form-label">Start Date</label>
|
|
<input type="date" class="form-control" id="startDate" name="start_date">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label for="endDate" class="form-label">End Date</label>
|
|
<input type="date" class="form-control" id="endDate" name="end_date">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="searchLogs" class="form-label">Search</label>
|
|
<input type="text" class="form-control" id="searchLogs" name="search" placeholder="Search in logs...">
|
|
</div>
|
|
<div class="col-md-6 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-primary me-2">
|
|
<i class="fas fa-search me-1"></i> Apply Filters
|
|
</button>
|
|
<button type="reset" class="btn btn-secondary">
|
|
<i class="fas fa-undo me-1"></i> Reset
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logs Table -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Timestamp</th>
|
|
<th>Level</th>
|
|
<th>Category</th>
|
|
<th>Action</th>
|
|
<th>Description</th>
|
|
<th>User</th>
|
|
<th>IP Address</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="logsTableBody">
|
|
<!-- Logs will be loaded here via JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<nav aria-label="Logs pagination" class="mt-4">
|
|
<ul class="pagination justify-content-center" id="logsPagination">
|
|
<!-- Pagination will be loaded here via JavaScript -->
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Details Modal -->
|
|
<div class="modal fade" id="logDetailsModal" tabindex="-1" aria-labelledby="logDetailsModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="logDetailsModalLabel">Log Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="logDetailsContent">
|
|
<!-- Log details will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Function to view log details
|
|
function viewLogDetails(logId) {
|
|
fetch(`/api/logs/${logId}`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch log details');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(log => {
|
|
const content = document.getElementById('logDetailsContent');
|
|
|
|
// Format the details in a readable way
|
|
let details = log.details;
|
|
let formattedDetails = 'No details available';
|
|
|
|
if (details) {
|
|
if (typeof details === 'string') {
|
|
formattedDetails = details.replace(/\n/g, '<br>');
|
|
} else if (typeof details === 'object') {
|
|
formattedDetails = JSON.stringify(details, null, 2).replace(/\n/g, '<br>');
|
|
} else {
|
|
formattedDetails = String(details);
|
|
}
|
|
}
|
|
|
|
content.innerHTML = `
|
|
<div class="mb-3">
|
|
<strong>Timestamp:</strong> ${new Date(log.timestamp).toLocaleString()}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Level:</strong> <span class="badge bg-${getLogLevelBadgeClass(log.level)}">${log.level}</span>
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Category:</strong> ${log.category || '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Action:</strong> ${log.action || '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Description:</strong> ${log.description || '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>User:</strong> ${log.user || '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>IP Address:</strong> ${log.ip_address || '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Details:</strong>
|
|
<div class="mt-2 p-3 bg-light rounded">
|
|
${formattedDetails}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Show modal
|
|
const modal = new bootstrap.Modal(document.getElementById('logDetailsModal'));
|
|
modal.show();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error viewing log details:', error);
|
|
showToast('Error loading log details', 'error');
|
|
});
|
|
}
|
|
|
|
// Helper function to get log level badge class
|
|
function getLogLevelBadgeClass(level) {
|
|
const classes = {
|
|
'INFO': 'info',
|
|
'WARNING': 'warning',
|
|
'ERROR': 'danger',
|
|
'DEBUG': 'secondary'
|
|
};
|
|
return classes[level] || 'secondary';
|
|
}
|
|
|
|
// Function to fetch logs
|
|
async function fetchLogs(page = 1) {
|
|
try {
|
|
const formData = new FormData(document.getElementById('logFiltersForm'));
|
|
const params = new URLSearchParams();
|
|
|
|
// Add form data to params
|
|
for (let [key, value] of formData.entries()) {
|
|
if (value) params.append(key, value);
|
|
}
|
|
|
|
// Add page number
|
|
params.append('page', page);
|
|
|
|
const response = await fetch(`/api/logs?${params.toString()}`);
|
|
if (!response.ok) throw new Error('Failed to fetch logs');
|
|
|
|
const data = await response.json();
|
|
const logsTableBody = document.getElementById('logsTableBody');
|
|
logsTableBody.innerHTML = '';
|
|
|
|
data.logs.forEach(log => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${new Date(log.timestamp).toLocaleString()}</td>
|
|
<td><span class="badge bg-${getLogLevelBadgeClass(log.level)}">${log.level}</span></td>
|
|
<td>${log.category || '-'}</td>
|
|
<td>${log.action || '-'}</td>
|
|
<td>${log.description || '-'}</td>
|
|
<td>${log.user || '-'}</td>
|
|
<td>${log.ip_address || '-'}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="viewLogDetails(${log.id})">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
logsTableBody.appendChild(row);
|
|
});
|
|
|
|
// Update pagination
|
|
updateLogsPagination(data.current_page, data.total_pages);
|
|
} catch (error) {
|
|
console.error('Error fetching logs:', error);
|
|
showToast('Error loading logs', 'error');
|
|
}
|
|
}
|
|
|
|
// Function to update logs pagination
|
|
function updateLogsPagination(currentPage, totalPages) {
|
|
const pagination = document.getElementById('logsPagination');
|
|
if (!pagination) return;
|
|
|
|
pagination.innerHTML = '';
|
|
|
|
// Previous button
|
|
const prevLi = document.createElement('li');
|
|
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
|
|
prevLi.innerHTML = `
|
|
<button class="page-link" onclick="fetchLogs(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>
|
|
<i class="fas fa-chevron-left"></i>
|
|
</button>
|
|
`;
|
|
pagination.appendChild(prevLi);
|
|
|
|
// Page numbers
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
const li = document.createElement('li');
|
|
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
|
|
li.innerHTML = `
|
|
<button class="page-link" onclick="fetchLogs(${i})">${i}</button>
|
|
`;
|
|
pagination.appendChild(li);
|
|
}
|
|
|
|
// Next button
|
|
const nextLi = document.createElement('li');
|
|
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
|
|
nextLi.innerHTML = `
|
|
<button class="page-link" onclick="fetchLogs(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>
|
|
<i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
`;
|
|
pagination.appendChild(nextLi);
|
|
}
|
|
|
|
// Event Listeners
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initial load
|
|
fetchLogs();
|
|
|
|
// Form submit handler
|
|
document.getElementById('logFiltersForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
fetchLogs(1);
|
|
});
|
|
|
|
// Form reset handler
|
|
document.getElementById('logFiltersForm').addEventListener('reset', function(e) {
|
|
e.preventDefault();
|
|
this.reset();
|
|
fetchLogs(1);
|
|
});
|
|
});
|
|
</script>
|
|
{% endmacro %} |