212 lines
6.1 KiB
JavaScript
212 lines
6.1 KiB
JavaScript
// users_log.js - Activity log and active users display
|
|
|
|
let logsPollingInterval = null;
|
|
let usersPollingInterval = null;
|
|
|
|
async function fetchActiveUsers() {
|
|
try {
|
|
const res = await fetch("/api/active_users", { cache: "no-store" });
|
|
if (!res.ok) {
|
|
console.error("Failed to fetch active users:", res.status);
|
|
return;
|
|
}
|
|
|
|
const data = await res.json();
|
|
displayActiveUsers(data.users || []);
|
|
} catch (err) {
|
|
console.error("Error fetching active users:", err);
|
|
}
|
|
}
|
|
|
|
function displayActiveUsers(users) {
|
|
const container = document.getElementById("active-users-list");
|
|
|
|
if (users.length === 0) {
|
|
container.innerHTML = '<div class="no-users">No active users</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = users.map(username => `
|
|
<div class="user-item">
|
|
<span class="user-indicator"></span>
|
|
<span class="user-name">${escapeHtml(username)}</span>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
async function fetchActivityLogs() {
|
|
try {
|
|
const res = await fetch("/api/activity_logs?limit=200", { cache: "no-store" });
|
|
if (!res.ok) {
|
|
console.error("Failed to fetch activity logs:", res.status);
|
|
return;
|
|
}
|
|
|
|
const data = await res.json();
|
|
displayActivityLogs(data.logs || []);
|
|
} catch (err) {
|
|
console.error("Error fetching activity logs:", err);
|
|
}
|
|
}
|
|
|
|
function displayActivityLogs(logs) {
|
|
const container = document.getElementById("activity-log-container");
|
|
|
|
if (logs.length === 0) {
|
|
container.innerHTML = '<div class="no-logs">No activity logs yet</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = logs.map(log => {
|
|
const time = formatTimestamp(log.timestamp);
|
|
const details = log.details ? ` - ${escapeHtml(log.details)}` : '';
|
|
|
|
return `
|
|
<div class="log-entry">
|
|
<span class="log-time">${time}</span>
|
|
<span class="log-user">${escapeHtml(log.username)}</span>
|
|
<span class="log-action">${escapeHtml(log.action)}</span>
|
|
${details ? `<span class="log-details">${details}</span>` : ''}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Auto-scroll to bottom on first load
|
|
if (!container.hasAttribute('data-scrolled')) {
|
|
container.scrollTop = container.scrollHeight;
|
|
container.setAttribute('data-scrolled', 'true');
|
|
}
|
|
}
|
|
|
|
function formatTimestamp(isoString) {
|
|
const date = new Date(isoString);
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
return `${hours}:${minutes}:${seconds}`;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
async function copyLogsToClipboard() {
|
|
try {
|
|
const res = await fetch("/api/activity_logs?limit=1000", { cache: "no-store" });
|
|
if (!res.ok) {
|
|
alert("Failed to fetch logs");
|
|
return;
|
|
}
|
|
|
|
const data = await res.json();
|
|
const logs = data.logs || [];
|
|
|
|
if (logs.length === 0) {
|
|
alert("No logs to copy");
|
|
return;
|
|
}
|
|
|
|
// Format logs as plain text
|
|
const logText = logs.map(log => {
|
|
const time = formatTimestamp(log.timestamp);
|
|
const details = log.details ? ` - ${log.details}` : '';
|
|
return `[${time}] ${log.username}: ${log.action}${details}`;
|
|
}).join('\n');
|
|
|
|
// Copy to clipboard
|
|
await navigator.clipboard.writeText(logText);
|
|
|
|
// Show success feedback
|
|
const btn = document.getElementById("copy-log-btn");
|
|
const originalText = btn.textContent;
|
|
btn.textContent = "Copied!";
|
|
btn.style.backgroundColor = "#4CAF50";
|
|
|
|
setTimeout(() => {
|
|
btn.textContent = originalText;
|
|
btn.style.backgroundColor = "";
|
|
}, 2000);
|
|
} catch (err) {
|
|
console.error("Error copying logs:", err);
|
|
alert("Failed to copy logs to clipboard");
|
|
}
|
|
}
|
|
|
|
function startPolling() {
|
|
// Fetch immediately
|
|
fetchActiveUsers();
|
|
fetchActivityLogs();
|
|
|
|
// Then poll at intervals
|
|
usersPollingInterval = setInterval(fetchActiveUsers, 10000); // Every 10 seconds
|
|
logsPollingInterval = setInterval(fetchActivityLogs, 5000); // Every 5 seconds
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (usersPollingInterval) {
|
|
clearInterval(usersPollingInterval);
|
|
usersPollingInterval = null;
|
|
}
|
|
if (logsPollingInterval) {
|
|
clearInterval(logsPollingInterval);
|
|
logsPollingInterval = null;
|
|
}
|
|
}
|
|
|
|
async function handleLogout() {
|
|
console.log("handleLogout called");
|
|
try {
|
|
console.log("Sending logout request to /auth/logout");
|
|
const response = await fetch("/auth/logout", {
|
|
method: "POST"
|
|
});
|
|
console.log("Logout response status:", response.status);
|
|
|
|
if (response.ok) {
|
|
console.log("Logout successful, redirecting to /login");
|
|
window.location.href = "/login";
|
|
} else {
|
|
console.error("Logout failed with status:", response.status);
|
|
window.location.href = "/login";
|
|
}
|
|
} catch (error) {
|
|
console.error("Error during logout:", error);
|
|
window.location.href = "/login";
|
|
}
|
|
}
|
|
|
|
function wireUp() {
|
|
// Attach copy button handler
|
|
const copyBtn = document.getElementById("copy-log-btn");
|
|
if (copyBtn) {
|
|
copyBtn.addEventListener("click", copyLogsToClipboard);
|
|
}
|
|
|
|
// Attach logout handler
|
|
const logoutBtn = document.getElementById("logout-btn");
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener("click", handleLogout);
|
|
}
|
|
}
|
|
|
|
// Initialize when page loads
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
wireUp();
|
|
startPolling();
|
|
});
|
|
|
|
// Stop polling when page is hidden/unloaded
|
|
document.addEventListener("visibilitychange", () => {
|
|
if (document.hidden) {
|
|
stopPolling();
|
|
} else {
|
|
startPolling();
|
|
}
|
|
});
|
|
|
|
window.addEventListener("beforeunload", () => {
|
|
stopPolling();
|
|
});
|