You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

539 lines
20 KiB

{% extends "base.html" %}
{% block title %}System Logs - Plutus{% endblock %}
{% block head %}
<style>
/* Background styling */
body {
background-color: #3a3a3a !important;
background-image: url("{{ url_for('static', filename='images/plutus3.JPG') }}") !important;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
position: relative;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(58, 58, 58, 0.85);
z-index: -1;
}
.container-fluid, .container {
position: relative;
z-index: 1;
}
/* Ensure navbar and footer are above background */
.navbar, .footer, main {
position: relative;
z-index: 1;
}
/* Page title and breadcrumb styling */
h1, h2, h3, h4, h5, h6 {
color: #faf8f0 !important;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}
.breadcrumb-item a {
color: #d4af37 !important;
}
.breadcrumb-item.active {
color: #faf8f0 !important;
}
.card {
background-color: rgba(250, 248, 240, 0.98) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.table-responsive {
background-color: rgba(250, 248, 240, 0.98);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.alert {
background-color: rgba(250, 248, 240, 0.98);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
</style>
{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('main.index') }}">Dashboard</a></li>
<li class="breadcrumb-item active" aria-current="page">System Logs</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-start mb-4">
<div>
<h1 class="h2 mb-1">System Logs</h1>
<p class="text-muted">User activity and system audit trail</p>
</div>
<div>
<button class="btn btn-info" onclick="exportLogs()">
<i class="fas fa-download"></i> Export Logs
</button>
</div>
</div>
<!-- Filter Controls -->
<div class="card shadow mb-4">
<div class="card-body">
<h2 class="h5 mb-3">
<i class="fas fa-filter"></i> Filters
</h2>
<div class="row g-3">
<div class="col-md-6 col-lg-4">
<label class="form-label form-label-sm">Search:</label>
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input class="form-control" type="text" id="searchInput" placeholder="Search logs, actions, details...">
</div>
</div>
<div class="col-md-6 col-lg-2">
<label class="form-label form-label-sm">User:</label>
<select class="form-select form-select-sm" id="userFilter">
<option value="">All Users</option>
{% for user in users %}
<option value="{{ user.id }}">{{ user.FullName }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6 col-lg-2">
<label class="form-label form-label-sm">Action:</label>
<select class="form-select form-select-sm" id="actionFilter">
<option value="">All Actions</option>
{% for action in actions %}
<option value="{{ action }}">{{ action }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6 col-lg-2">
<label class="form-label form-label-sm">Entity Type:</label>
<select class="form-select form-select-sm" id="entityTypeFilter">
<option value="">All Types</option>
{% for entity_type in entity_types %}
<option value="{{ entity_type }}">{{ entity_type }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6 col-lg-3">
<label class="form-label form-label-sm">Date From:</label>
<input class="form-control form-control-sm" type="date" id="dateFromFilter">
</div>
<div class="col-md-6 col-lg-3">
<label class="form-label form-label-sm">Date To:</label>
<input class="form-control form-control-sm" type="date" id="dateToFilter">
</div>
<div class="col-md-6 col-lg-3 d-flex align-items-end">
<button class="btn btn-sm btn-info me-2" onclick="applyFilters()">
<i class="fas fa-search"></i> Apply Filters
</button>
<button class="btn btn-sm btn-light" onclick="clearFilters()">
<i class="fas fa-times"></i> Clear
</button>
</div>
</div>
</div>
</div>
<!-- Results Summary -->
<div class="alert alert-info" id="filterResults" style="display: none;">
<span id="resultCount">0</span> of {{ logs|length }} log entries shown
</div>
<!-- Logs Table -->
<div class="card shadow">
<div class="card-body">
<h2 class="h5 mb-3">
<i class="fas fa-list"></i> Log Entries
</h2>
{% if logs %}
<div class="table-responsive">
<table class="table table-striped table-hover" id="logsTable">
<thead>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Entity</th>
<th>Details</th>
<th>IP Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="logsTableBody">
{% for log in logs %}
<tr>
<td>
<small class="d-block">{{ log.Added.strftime('%Y-%m-%d') }}</small>
<small class="text-muted">{{ log.Added.strftime('%H:%M:%S') }}</small>
</td>
<td>
<div>
<strong>{{ log.user_name or 'System' }}</strong>
{% if log.User_ID %}
<br><small class="text-muted">ID: {{ log.User_ID }}</small>
{% endif %}
</div>
</td>
<td>
{% if log.Action %}
<span class="badge bg-info">{{ log.Action }}</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if log.Entity_Type %}
<div>
<span class="badge bg-primary">{{ log.Entity_Type }}</span>
{% if log.Entity_ID %}
<br><small class="text-muted">ID: {{ log.Entity_ID }}</small>
{% endif %}
</div>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if log.Log_Entry %}
<small>
{{ log.Log_Entry[:100] }}{% if log.Log_Entry|length > 100 %}...{% endif %}
</small>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if log.IP_Address %}
<code class="small">{{ log.IP_Address }}</code>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
<button class="btn btn-info btn-sm" onclick="showLogDetail({{ log.id }})">
<i class="fas fa-eye"></i> View
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if pagination %}
<nav aria-label="Logs pagination">
<ul class="pagination justify-content-center mt-3">
<li class="page-item {% if not pagination.has_prev %}disabled{% endif %}">
<a class="page-link" href="{% if pagination.has_prev %}{{ url_for('main.logs_list', page=pagination.prev_num, **request.args) }}{% else %}#{% endif %}">Previous</a>
</li>
{% for page_num in pagination.iter_pages() %}
{% if page_num %}
<li class="page-item {% if page_num == pagination.page %}active{% endif %}">
<a class="page-link" href="{{ url_for('main.logs_list', page=page_num, **request.args) }}">{{ page_num }}</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% endfor %}
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
<a class="page-link" href="{% if pagination.has_next %}{{ url_for('main.logs_list', page=pagination.next_num, **request.args) }}{% else %}#{% endif %}">Next</a>
</li>
</ul>
</nav>
{% endif %}
{% else %}
<div class="alert alert-info">
<p class="mb-0">No log entries found.</p>
</div>
{% endif %}
</div>
</div>
<!-- Log Detail Modal -->
<div class="modal fade" id="logDetailModal" tabindex="-1" aria-labelledby="logDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="logDetailModalLabel">
<i class="fas fa-file-alt"></i> Log Entry Details
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="logDetailContent">
<!-- Log details will be populated here -->
</div>
</div>
<div class="modal-footer">
<button class="btn btn-info" onclick="copyLogDetails()">
<i class="fas fa-copy"></i> Copy Details
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
let allLogs = [];
let filteredLogs = [];
// Initialize logs and filters when page loads
document.addEventListener('DOMContentLoaded', function() {
initializeLogs();
setupEventListeners();
});
function initializeLogs() {
const tableBody = document.getElementById('logsTableBody');
const rows = tableBody.querySelectorAll('tr');
allLogs = Array.from(rows).map(row => {
const cells = row.querySelectorAll('td');
return {
element: row,
timestamp: cells[0] ? (cells[0].textContent.trim() || '') : '',
user: cells[1] ? (cells[1].textContent.trim() || '') : '',
action: cells[2] ? (cells[2].textContent.trim() || '') : '',
entityType: cells[3] ? (cells[3].textContent.trim() || '') : '',
details: cells[4] ? (cells[4].textContent.trim() || '') : '',
ipAddress: cells[5] ? (cells[5].textContent.trim() || '') : ''
};
});
filteredLogs = [...allLogs];
updateResultCount();
}
function setupEventListeners() {
document.getElementById('searchInput').addEventListener('input', applyFilters);
document.getElementById('userFilter').addEventListener('change', applyFilters);
document.getElementById('actionFilter').addEventListener('change', applyFilters);
document.getElementById('entityTypeFilter').addEventListener('change', applyFilters);
document.getElementById('dateFromFilter').addEventListener('change', applyFilters);
document.getElementById('dateToFilter').addEventListener('change', applyFilters);
}
function applyFilters() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const userFilter = document.getElementById('userFilter').value;
const actionFilter = document.getElementById('actionFilter').value;
const entityTypeFilter = document.getElementById('entityTypeFilter').value;
const dateFromFilter = document.getElementById('dateFromFilter').value;
const dateToFilter = document.getElementById('dateToFilter').value;
// Filter logs
filteredLogs = allLogs.filter(log => {
// Search filter
const searchMatch = !searchTerm ||
log.user.toLowerCase().includes(searchTerm) ||
log.action.toLowerCase().includes(searchTerm) ||
log.entityType.toLowerCase().includes(searchTerm) ||
log.details.toLowerCase().includes(searchTerm) ||
log.ipAddress.toLowerCase().includes(searchTerm);
// User filter
const userMatch = !userFilter || log.user.includes(`ID: ${userFilter}`);
// Action filter
const actionMatch = !actionFilter || log.action.includes(actionFilter);
// Entity type filter
const entityTypeMatch = !entityTypeFilter || log.entityType.includes(entityTypeFilter);
// Date filters would need server-side implementation for full functionality
// For now, we'll implement basic client-side date filtering on visible text
let dateMatch = true;
if (dateFromFilter || dateToFilter) {
const logDate = log.timestamp.split(' ')[0]; // Get just the date part
if (dateFromFilter && logDate < dateFromFilter) dateMatch = false;
if (dateToFilter && logDate > dateToFilter) dateMatch = false;
}
return searchMatch && userMatch && actionMatch && entityTypeMatch && dateMatch;
});
// Update display
updateTable();
updateResultCount();
}
function updateTable() {
const tableBody = document.getElementById('logsTableBody');
// Hide all rows first
allLogs.forEach(log => {
log.element.style.display = 'none';
});
// Show filtered rows
filteredLogs.forEach(log => {
log.element.style.display = '';
tableBody.appendChild(log.element); // Re-append to maintain order
});
}
function updateResultCount() {
const resultCount = document.getElementById('resultCount');
const filterResults = document.getElementById('filterResults');
resultCount.textContent = filteredLogs.length;
if (filteredLogs.length === allLogs.length) {
filterResults.style.display = 'none';
} else {
filterResults.style.display = 'block';
}
}
function clearFilters() {
document.getElementById('searchInput').value = '';
document.getElementById('userFilter').value = '';
document.getElementById('actionFilter').value = '';
document.getElementById('entityTypeFilter').value = '';
document.getElementById('dateFromFilter').value = '';
document.getElementById('dateToFilter').value = '';
applyFilters();
}
function showLogDetail(logId) {
// Fetch log details via AJAX
fetch(`/logs/detail/${logId}`)
.then(response => response.json())
.then(data => {
if (data.success) {
const log = data.log;
const detailHtml = `
<div>
<table class="table table-bordered">
<tbody>
<tr>
<td><strong>ID</strong></td>
<td>#${log.id}</td>
</tr>
<tr>
<td><strong>Timestamp</strong></td>
<td>${log.timestamp}</td>
</tr>
<tr>
<td><strong>User</strong></td>
<td>${log.user_name || 'System'} ${log.User_ID ? `(ID: ${log.User_ID})` : ''}</td>
</tr>
<tr>
<td><strong>Action</strong></td>
<td>${log.Action || '-'}</td>
</tr>
<tr>
<td><strong>Entity Type</strong></td>
<td>${log.Entity_Type || '-'}</td>
</tr>
<tr>
<td><strong>Entity ID</strong></td>
<td>${log.Entity_ID || '-'}</td>
</tr>
<tr>
<td><strong>IP Address</strong></td>
<td>${log.IP_Address || '-'}</td>
</tr>
</tbody>
</table>
${log.Log_Entry ? `
<div class="mb-3">
<label class="form-label"><strong>Full Details:</strong></label>
<div class="card">
<div class="card-body">
<pre class="mb-0">${log.Log_Entry}</pre>
</div>
</div>
</div>
` : ''}
</div>
`;
document.getElementById('logDetailContent').innerHTML = detailHtml;
const modal = new bootstrap.Modal(document.getElementById('logDetailModal'));
modal.show();
} else {
alert('Failed to load log details: ' + data.error);
}
})
.catch(error => {
console.error('Error fetching log details:', error);
alert('Failed to load log details. Please try again.');
});
}
function copyLogDetails() {
const content = document.getElementById('logDetailContent').innerText;
navigator.clipboard.writeText(content).then(function() {
// Show temporary success message
const button = event.target.closest('button');
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-check"></i> Copied!';
button.classList.remove('btn-info');
button.classList.add('btn-success');
setTimeout(function() {
button.innerHTML = originalText;
button.classList.remove('btn-success');
button.classList.add('btn-info');
}, 2000);
}).catch(function(err) {
console.error('Failed to copy text: ', err);
alert('Failed to copy to clipboard');
});
}
function exportLogs() {
// Create form data with current filters
const params = new URLSearchParams();
const searchTerm = document.getElementById('searchInput').value;
const userFilter = document.getElementById('userFilter').value;
const actionFilter = document.getElementById('actionFilter').value;
const entityTypeFilter = document.getElementById('entityTypeFilter').value;
const dateFromFilter = document.getElementById('dateFromFilter').value;
const dateToFilter = document.getElementById('dateToFilter').value;
if (searchTerm) params.append('search', searchTerm);
if (userFilter) params.append('user', userFilter);
if (actionFilter) params.append('action', actionFilter);
if (entityTypeFilter) params.append('entity_type', entityTypeFilter);
if (dateFromFilter) params.append('date_from', dateFromFilter);
if (dateToFilter) params.append('date_to', dateToFilter);
// Open export URL in new window
window.open(`/logs/export?${params.toString()}`, '_blank');
}
</script>
{% endblock %}