Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/main/resources/static/admin-audit-log.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audit Log - Admin</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 p-6">
<div class="max-w-7xl mx-auto">
<h1 class="text-3xl font-bold mb-6">📋 Audit Log</h1>

<div class="mb-6 bg-white p-4 rounded-lg shadow flex gap-2">
<input type="text" id="searchInput" placeholder="Search user or action..."
class="border rounded px-4 py-2 w-64">
<button onclick="searchLogs()" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">Search</button>
<button onclick="loadLogs()" class="bg-gray-600 text-white px-4 py-2 rounded hover:bg-gray-700">Show All</button>
</div>

<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-500">Time</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-500">User</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-500">Action</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-500">Target</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-500">Details</th>
</tr>
</thead>
<tbody id="logBody" class="divide-y divide-gray-200"></tbody>
</table>
</div>
</div>

<script>
const API = '/api/admin/audit';
const token = localStorage.getItem('token');

document.getElementById('searchInput').addEventListener('keypress', e => {
if (e.key === 'Enter') searchLogs();
});

async function loadLogs() {
document.getElementById('searchInput').value = '';
await fetchLogs(API + '?size=20');
}

async function searchLogs() {
const q = document.getElementById('searchInput').value.trim();
if (!q) return loadLogs();
await fetchLogs(API + '?search=' + encodeURIComponent(q) + '&size=20');
}

async function fetchLogs(url) {
try {
const res = await fetch(url, { headers: { 'Authorization': 'Bearer ' + token } });
if (!res.ok) {
console.warn('Audit log fetch failed:', res.status);
document.getElementById('logBody').innerHTML =
'<tr><td colspan="5" class="p-4 text-center text-red-500">⚠️ Access denied or no data</td></tr>';
return;
}
const data = await res.json();
renderLogs(data.content || data);
} catch (e) {
console.error('Audit log fetch error:', e);
document.getElementById('logBody').innerHTML =
'<tr><td colspan="5" class="p-4 text-center text-red-500">⚠️ Network error</td></tr>';
}
}

function renderLogs(logs) {
const tbody = document.getElementById('logBody');
if (!logs.length) {
tbody.innerHTML = '<tr><td colspan="5" class="p-4 text-center text-gray-500">No logs found</td></tr>';
return;
}
tbody.innerHTML = logs.map(log => `
<tr class="hover:bg-gray-50">
<td class="p-2 text-sm">${new Date(log.createdAt).toLocaleString()}</td>
<td class="p-2 text-sm">${log.userName || '-'}</td>
<td class="p-2 text-sm">
<span class="px-2 py-0.5 rounded text-xs font-medium ${actionColor(log.action)}">${log.action}</span>
</td>
<td class="p-2 text-sm">${log.targetType || '-'} #${log.targetId || ''}</td>
<td class="p-2 text-sm">${log.details || '-'}</td>
</tr>
`).join('');
}

function actionColor(action) {
const colors = {
'CREATE': 'bg-green-100 text-green-800', 'DELETE': 'bg-red-100 text-red-800',
'UPDATE': 'bg-blue-100 text-blue-800', 'VIEW': 'bg-gray-100 text-gray-800',
'ASSIGN': 'bg-purple-100 text-purple-800', 'CLOSE': 'bg-yellow-100 text-yellow-800'
};
for (const key in colors) {
if (action && action.includes(key)) return colors[key];
}
return 'bg-gray-100 text-gray-800';
}
loadLogs();

</script>
</body>
</html>
Loading