Files
uldp-degree-mangement-system/views/admin/audit-log/index.ejs
2026-04-11 14:08:27 +07:00

244 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- Page Header -->
<div class="page-title-area">
<div>
<h1>Audit Logs</h1>
<p class="subtitle">System activity and change tracking</p>
</div>
<button type="button" class="btn btn-outline-warning" onclick="openCleanupModal()">
<i class="fas fa-broom"></i> Cleanup Old Logs
</button>
</div>
<!-- Filters -->
<div class="card border-0 mb-3">
<div class="card-body" style="padding:1rem 1.25rem;">
<form method="GET" action="/admin/audit-logs">
<div class="row g-2 align-items-end">
<div class="col-md-2">
<label class="form-label">Model</label>
<select class="form-select" name="model">
<option value="">All Models</option>
<% uniqueModels.forEach(model => { %>
<option value="<%= model %>" <%= query.model === model ? 'selected' : '' %>><%= model %></option>
<% }); %>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Action</label>
<select class="form-select" name="action">
<option value="">All Actions</option>
<% uniqueActions.forEach(action => { %>
<option value="<%= action %>" <%= query.action === action ? 'selected' : '' %>><%= action %></option>
<% }); %>
</select>
</div>
<div class="col-md-2">
<label class="form-label">User</label>
<select class="form-select" name="user">
<option value="">All Users</option>
<% users.forEach(u => { %>
<option value="<%= u._id %>" <%= query.user === u._id.toString() ? 'selected' : '' %>><%= u.username %></option>
<% }); %>
</select>
</div>
<div class="col-md-2">
<label class="form-label">From Date</label>
<input type="date" class="form-control" name="dateFrom" value="<%= query.dateFrom || '' %>">
</div>
<div class="col-md-2">
<label class="form-label">To Date</label>
<input type="date" class="form-control" name="dateTo" value="<%= query.dateTo || '' %>">
</div>
<div class="col-md-1">
<label class="form-label">Per page</label>
<select class="form-select" name="limit" onchange="this.form.submit()">
<option value="8" <%= (!query.limit || query.limit == '8') ? 'selected' : '' %>>8</option>
<option value="15" <%= query.limit == '15' ? 'selected' : '' %>>15</option>
<option value="25" <%= query.limit == '25' ? 'selected' : '' %>>25</option>
</select>
</div>
<div class="col-md-1 d-flex gap-1">
<button type="submit" class="btn btn-primary flex-fill"><i class="fas fa-search"></i></button>
<a href="/admin/audit-logs" class="btn btn-outline-secondary"><i class="fas fa-times"></i></a>
</div>
</div>
</form>
</div>
</div>
<!-- Table -->
<div class="card border-0">
<div class="card-header">
<h5 class="card-header-title"><i class="fas fa-shield-alt"></i> Activity Log</h5>
<% if (pagination) { %>
<span class="badge badge-soft-primary"><%= pagination.totalCount %> records</span>
<% } %>
</div>
<div class="card-body p-0">
<% if (auditLogs && auditLogs.length > 0) { %>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th style="width:130px">Date / Time</th>
<th>Model</th>
<th>Action</th>
<th>User</th>
<th>Changes</th>
<th>IP Address</th>
<th style="width:80px">Details</th>
</tr>
</thead>
<tbody>
<% auditLogs.forEach(log => { %>
<tr>
<td>
<div style="font-size:0.8rem;font-weight:500"><%= new Date(log.createdAt).toLocaleDateString('en-GB') %></div>
<div style="font-size:0.75rem;color:var(--text-muted)"><%= new Date(log.createdAt).toLocaleTimeString() %></div>
</td>
<td><span class="badge badge-soft-primary"><%= log.model %></span></td>
<td>
<%
let badgeClass = 'badge-soft-primary';
if (log.action.includes('CREATE')) badgeClass = 'bg-soft-success';
else if (log.action.includes('UPDATE')) badgeClass = 'bg-soft-warning';
else if (log.action.includes('DELETE')) badgeClass = 'bg-soft-danger';
%>
<span class="badge <%= badgeClass %>"><%= log.action %></span>
</td>
<td>
<% if (log.performedBy) { %>
<div style="font-weight:500;font-size:0.8125rem"><%= log.performedBy.username %></div>
<div style="font-size:0.75rem;color:var(--text-muted)"><%= log.performedBy.email %></div>
<% } else { %>
<span style="color:var(--text-muted);font-size:0.8125rem">System</span>
<% } %>
</td>
<td>
<% if (log.changes && log.changes.length > 0) { %>
<span class="badge badge-soft-accent"><%= log.changes.length %> field<%= log.changes.length > 1 ? 's' : '' %></span>
<div class="mt-1">
<% log.changes.slice(0, 2).forEach(change => { %>
<div style="font-size:0.75rem;color:var(--text-muted)"><%= change.field %></div>
<% }); %>
<% if (log.changes.length > 2) { %>
<div style="font-size:0.75rem;color:var(--text-muted)">+<%= log.changes.length - 2 %> more</div>
<% } %>
</div>
<% } else { %>
<span style="color:var(--text-muted)">—</span>
<% } %>
</td>
<td style="font-size:0.8rem;color:var(--text-muted)"><%= log.ipAddress %></td>
<td>
<a href="/admin/audit-logs/<%= log._id %>" class="btn btn-sm btn-outline-primary btn-icon" title="View">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
<% }); %>
</tbody>
</table>
</div>
<!-- Pagination -->
<% if (pagination && pagination.total > 1) { %>
<div class="d-flex align-items-center justify-content-between px-3 py-2" style="border-top:1px solid var(--border-light);">
<small style="color:var(--text-muted)">
Showing <%= ((pagination.current - 1) * pagination.limit) + 1 %><%= Math.min(pagination.current * pagination.limit, pagination.totalCount) %> of <%= pagination.totalCount %>
</small>
<nav>
<ul class="pagination pagination-sm mb-0" style="gap:2px;">
<% if (pagination.current > 1) { %>
<li class="page-item">
<a class="page-link" href="?page=<%= pagination.current - 1 %><%= Object.keys(query).map(k => k !== 'page' ? '&' + k + '=' + encodeURIComponent(query[k]) : '').join('') %>">
<i class="fas fa-chevron-left" style="font-size:0.7rem"></i>
</a>
</li>
<% } %>
<% for (let i = 1; i <= pagination.total; i++) { %>
<% if (i === pagination.current) { %>
<li class="page-item active">
<span class="page-link" style="background:var(--primary-color);border-color:var(--primary-color)"><%= i %></span>
</li>
<% } else if (i === 1 || i === pagination.total || (i >= pagination.current - 2 && i <= pagination.current + 2)) { %>
<li class="page-item">
<a class="page-link" href="?page=<%= i %><%= Object.keys(query).map(k => k !== 'page' ? '&' + k + '=' + encodeURIComponent(query[k]) : '').join('') %>"><%= i %></a>
</li>
<% } else if (i === pagination.current - 3 || i === pagination.current + 3) { %>
<li class="page-item disabled"><span class="page-link">…</span></li>
<% } %>
<% } %>
<% if (pagination.current < pagination.total) { %>
<li class="page-item">
<a class="page-link" href="?page=<%= pagination.current + 1 %><%= Object.keys(query).map(k => k !== 'page' ? '&' + k + '=' + encodeURIComponent(query[k]) : '').join('') %>">
<i class="fas fa-chevron-right" style="font-size:0.7rem"></i>
</a>
</li>
<% } %>
</ul>
</nav>
</div>
<% } %>
<% } else { %>
<div class="empty-state">
<div class="empty-state-icon"><i class="fas fa-shield-alt"></i></div>
<h5>No audit logs found</h5>
<p>No activity matches your current filters.</p>
<a href="/admin/audit-logs" class="btn btn-outline-primary">Clear Filters</a>
</div>
<% } %>
</div>
</div>
<!-- Cleanup Modal -->
<div id="cleanupModal" style="display:none;position:fixed;inset:0;z-index:1050;">
<div style="position:absolute;inset:0;background:rgba(0,0,0,0.45);" onclick="closeCleanupModal()"></div>
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;border-radius:var(--border-radius-lg);box-shadow:var(--shadow-lg);width:90%;max-width:460px;overflow:hidden;">
<div style="padding:1rem 1.25rem;background:var(--warning-soft);border-bottom:1px solid var(--border-color);display:flex;align-items:center;justify-content:space-between;">
<span style="font-weight:600;color:var(--warning-color);display:flex;align-items:center;gap:0.5rem;">
<i class="fas fa-broom"></i> Cleanup Old Audit Logs
</span>
<button onclick="closeCleanupModal()" style="background:none;border:none;cursor:pointer;color:var(--warning-color);font-size:1.1rem;"><i class="fas fa-times"></i></button>
</div>
<form method="POST" action="/admin/audit-logs/cleanup">
<div style="padding:1.25rem;">
<p style="font-size:var(--font-size-sm);color:var(--text-muted);margin-bottom:1rem;">Delete audit logs older than the specified number of days.</p>
<div class="mb-3">
<label class="form-label">Keep logs for (days)</label>
<input type="number" class="form-control" name="days" value="90" min="1" max="365" required>
<div class="form-text">Recommended: 90 days for compliance</div>
</div>
<div class="alert d-flex gap-2 align-items-start" style="background:var(--warning-soft);border:1px solid rgba(217,119,6,0.2);border-radius:var(--border-radius-sm);padding:0.75rem;">
<i class="fas fa-exclamation-triangle" style="color:var(--warning-color);margin-top:2px;flex-shrink:0;"></i>
<span style="font-size:var(--font-size-sm);color:var(--warning-color);">This action cannot be undone. Deleted logs will be permanently removed.</span>
</div>
</div>
<div style="padding:0.875rem 1.25rem;border-top:1px solid var(--border-color);display:flex;gap:0.5rem;justify-content:flex-end;background:#fafbfc;">
<button type="button" class="btn btn-outline-secondary" onclick="closeCleanupModal()">Cancel</button>
<button type="submit" class="btn btn-warning"><i class="fas fa-broom"></i> Cleanup</button>
</div>
</form>
</div>
</div>
<style>
.pagination .page-link {
color: var(--primary-color);
border-color: var(--border-color);
border-radius: var(--border-radius-sm) !important;
font-size: 0.8125rem;
padding: 0.3rem 0.6rem;
}
.pagination .page-link:hover { background: var(--primary-soft); border-color: var(--primary-color); }
.pagination .page-item.active .page-link { color: #fff; }
.pagination .page-item.disabled .page-link { color: var(--text-muted); }
</style>
<script>
function openCleanupModal() { document.getElementById('cleanupModal').style.display = 'block'; }
function closeCleanupModal() { document.getElementById('cleanupModal').style.display = 'none'; }
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeCleanupModal(); });
</script>