486 lines
14 KiB
JavaScript
486 lines
14 KiB
JavaScript
/**
|
|
* Logger Manager - Modern activity log management for the unified logger view
|
|
*/
|
|
|
|
class LoggerManager {
|
|
constructor(options = {}) {
|
|
this.categories = options.categories || [];
|
|
this.initialFilters = options.initialFilters || {};
|
|
|
|
// Pagination state
|
|
this.currentPage = 1;
|
|
this.perPage = 50;
|
|
this.totalPages = 1;
|
|
this.totalEntries = 0;
|
|
|
|
// Current filter state
|
|
this.filters = { ...this.initialFilters };
|
|
|
|
// DOM elements
|
|
this.initElements();
|
|
this.initEventListeners();
|
|
|
|
// Apply initial filters and load data
|
|
this.applyInitialFilters();
|
|
this.loadLogs();
|
|
}
|
|
|
|
initElements() {
|
|
// Form elements
|
|
this.filtersForm = document.getElementById("filterForm");
|
|
this.categoryCheckboxes = document.querySelectorAll(".category-checkbox");
|
|
this.selectAllCategories = document.getElementById("selectAllCategories");
|
|
this.statusSelect = document.getElementById("statusFilter");
|
|
this.startDateInput = document.getElementById("startDate");
|
|
this.endDateInput = document.getElementById("endDate");
|
|
this.searchTermInput = document.getElementById("searchTerm");
|
|
this.clearFiltersBtn = document.getElementById("clearFilters");
|
|
this.downloadLogsBtn = document.getElementById("downloadLogs");
|
|
this.refreshLogsBtn = document.getElementById("refreshLogs");
|
|
|
|
// Logs display elements
|
|
this.logsTableBody = document.getElementById("logsTableBody");
|
|
this.pageSizeSelect = document.getElementById("pageSize");
|
|
|
|
// Pagination elements
|
|
this.paginationContainer = document.getElementById("logsPagination");
|
|
this.paginationInfo = document.getElementById("paginationDetails");
|
|
this.prevPageBtn = document.getElementById("prevPage");
|
|
this.nextPageBtn = document.getElementById("nextPage");
|
|
this.currentPageSpan = document.getElementById("currentPageSpan");
|
|
|
|
// Modal
|
|
this.logModal = new ModalHandler("logDetailModal", "log-detail-content");
|
|
}
|
|
|
|
initEventListeners() {
|
|
// Filter form submission
|
|
if (this.filtersForm) {
|
|
this.filtersForm.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
this.applyFilters();
|
|
});
|
|
}
|
|
|
|
// Handle "Select All" checkbox for categories
|
|
if (this.selectAllCategories) {
|
|
this.selectAllCategories.addEventListener("change", () => {
|
|
const isChecked = this.selectAllCategories.checked;
|
|
this.categoryCheckboxes.forEach((checkbox) => {
|
|
checkbox.checked = isChecked;
|
|
});
|
|
this.applyFilters();
|
|
});
|
|
}
|
|
|
|
// Handle individual category checkboxes
|
|
this.categoryCheckboxes.forEach((checkbox) => {
|
|
checkbox.addEventListener("change", () => {
|
|
// Update "Select All" checkbox state
|
|
this.updateSelectAllState();
|
|
this.applyFilters();
|
|
});
|
|
});
|
|
|
|
// Individual filter changes for immediate application
|
|
[this.statusSelect, this.startDateInput, this.endDateInput].forEach(
|
|
(element) => {
|
|
if (element) {
|
|
element.addEventListener("change", () => {
|
|
this.applyFilters();
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
// Search term with debounce
|
|
if (this.searchTermInput) {
|
|
let searchTimeout;
|
|
this.searchTermInput.addEventListener("input", () => {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
this.applyFilters();
|
|
}, 500);
|
|
});
|
|
}
|
|
|
|
// Clear filters
|
|
if (this.clearFiltersBtn) {
|
|
this.clearFiltersBtn.addEventListener("click", () => {
|
|
this.clearAllFilters();
|
|
});
|
|
}
|
|
|
|
// Download logs
|
|
if (this.downloadLogsBtn) {
|
|
this.downloadLogsBtn.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
this.downloadLogs();
|
|
});
|
|
}
|
|
|
|
// Refresh logs
|
|
if (this.refreshLogsBtn) {
|
|
this.refreshLogsBtn.addEventListener("click", () => {
|
|
this.loadLogs();
|
|
});
|
|
}
|
|
|
|
// Page size change
|
|
if (this.pageSizeSelect) {
|
|
this.pageSizeSelect.addEventListener("change", () => {
|
|
this.perPage = parseInt(this.pageSizeSelect.value);
|
|
this.currentPage = 1; // Reset to first page
|
|
this.loadLogs();
|
|
});
|
|
}
|
|
|
|
// Pagination buttons
|
|
if (this.prevPageBtn) {
|
|
this.prevPageBtn.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
if (this.currentPage > 1) {
|
|
this.currentPage--;
|
|
this.loadLogs();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (this.nextPageBtn) {
|
|
this.nextPageBtn.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
if (this.currentPage < this.totalPages) {
|
|
this.currentPage++;
|
|
this.loadLogs();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
updateSelectAllState() {
|
|
const checkedCount = Array.from(this.categoryCheckboxes).filter(
|
|
(cb) => cb.checked
|
|
).length;
|
|
const totalCount = this.categoryCheckboxes.length;
|
|
|
|
if (checkedCount === 0) {
|
|
this.selectAllCategories.checked = false;
|
|
this.selectAllCategories.indeterminate = false;
|
|
} else if (checkedCount === totalCount) {
|
|
this.selectAllCategories.checked = true;
|
|
this.selectAllCategories.indeterminate = false;
|
|
} else {
|
|
this.selectAllCategories.checked = false;
|
|
this.selectAllCategories.indeterminate = true;
|
|
}
|
|
}
|
|
|
|
getSelectedCategories() {
|
|
return Array.from(this.categoryCheckboxes)
|
|
.filter((checkbox) => checkbox.checked)
|
|
.map((checkbox) => checkbox.value);
|
|
}
|
|
|
|
applyInitialFilters() {
|
|
// Set category checkboxes from initial filters
|
|
if (this.initialFilters.category) {
|
|
const selectedCategories = Array.isArray(this.initialFilters.category)
|
|
? this.initialFilters.category
|
|
: [this.initialFilters.category];
|
|
|
|
this.categoryCheckboxes.forEach((checkbox) => {
|
|
checkbox.checked = selectedCategories.includes(checkbox.value);
|
|
});
|
|
this.updateSelectAllState();
|
|
}
|
|
|
|
if (this.startDateInput && this.initialFilters.start_date) {
|
|
this.startDateInput.value = this.initialFilters.start_date;
|
|
}
|
|
if (this.endDateInput && this.initialFilters.end_date) {
|
|
this.endDateInput.value = this.initialFilters.end_date;
|
|
}
|
|
if (this.searchTermInput && this.initialFilters.search_term) {
|
|
this.searchTermInput.value = this.initialFilters.search_term;
|
|
}
|
|
}
|
|
|
|
applyFilters() {
|
|
// Collect current filter values
|
|
const selectedCategories = this.getSelectedCategories();
|
|
|
|
this.filters = {
|
|
category: selectedCategories, // Now an array
|
|
status: this.statusSelect?.value || "",
|
|
start_date: this.startDateInput?.value || "",
|
|
end_date: this.endDateInput?.value || "",
|
|
search_term: this.searchTermInput?.value || "",
|
|
};
|
|
|
|
// Reset to first page when filters change
|
|
this.currentPage = 1;
|
|
|
|
// Load logs with new filters
|
|
this.loadLogs();
|
|
|
|
// Update URL to reflect current filters (for bookmarking/sharing)
|
|
this.updateUrl();
|
|
}
|
|
|
|
clearAllFilters() {
|
|
// Clear all category checkboxes and select all
|
|
this.categoryCheckboxes.forEach((checkbox) => {
|
|
checkbox.checked = true; // Default to all selected
|
|
});
|
|
if (this.selectAllCategories) {
|
|
this.selectAllCategories.checked = true;
|
|
this.selectAllCategories.indeterminate = false;
|
|
}
|
|
|
|
if (this.statusSelect) this.statusSelect.value = "";
|
|
if (this.startDateInput) this.startDateInput.value = "";
|
|
if (this.endDateInput) this.endDateInput.value = "";
|
|
if (this.searchTermInput) this.searchTermInput.value = "";
|
|
|
|
// Apply empty filters
|
|
this.applyFilters();
|
|
}
|
|
|
|
async loadLogs() {
|
|
if (!this.logsTableBody) return;
|
|
|
|
try {
|
|
// Show loading state
|
|
this.logsTableBody.innerHTML =
|
|
'<tr><td colspan="5" class="text-center"><div class="spinner-border spinner-border-sm text-primary" role="status"><span class="visually-hidden">Loading...</span></div> Loading logs...</td></tr>';
|
|
|
|
// Build query parameters
|
|
const params = new URLSearchParams({
|
|
page: this.currentPage,
|
|
per_page: this.perPage,
|
|
});
|
|
|
|
// Add filters to query
|
|
Object.entries(this.filters).forEach(([key, value]) => {
|
|
if (value) {
|
|
if (key === "category" && Array.isArray(value)) {
|
|
// Handle multiple categories
|
|
value.forEach((cat) => {
|
|
if (cat) params.append("category", cat);
|
|
});
|
|
} else if (value) {
|
|
params.append(key, value);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Fetch logs from unified API
|
|
const data = await apiRequest(`/logs/api?${params.toString()}`);
|
|
|
|
if (data.success) {
|
|
this.renderLogs(data.logs);
|
|
this.updatePagination(data.pagination);
|
|
console.log("Logs loaded successfully");
|
|
} else {
|
|
throw new Error(data.message || "Failed to load logs");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load logs:", error);
|
|
this.logsTableBody.innerHTML =
|
|
'<tr><td colspan="5" class="text-center text-danger">Error loading logs. Please try again.</td></tr>';
|
|
this.hidePagination();
|
|
}
|
|
}
|
|
|
|
renderLogs(logs) {
|
|
if (!this.logsTableBody) return;
|
|
|
|
this.logsTableBody.innerHTML = "";
|
|
|
|
if (!logs || logs.length === 0) {
|
|
this.logsTableBody.innerHTML =
|
|
'<tr><td colspan="5" class="text-center">No logs found matching the current filters.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
logs.forEach((log) => {
|
|
const row = document.createElement("tr");
|
|
row.className = "log-entry";
|
|
row.setAttribute("data-log-id", log.id);
|
|
|
|
// Format timestamp
|
|
const timeStr = formatTimestamp(log.timestamp);
|
|
|
|
// Create status badge
|
|
const statusBadge = createStatusBadge(log.status);
|
|
|
|
// Create category badge
|
|
const categoryBadge = this.createCategoryBadge(log.category);
|
|
|
|
row.innerHTML = `
|
|
<td>${timeStr}</td>
|
|
<td>${categoryBadge}</td>
|
|
<td>${log.action}</td>
|
|
<td>${statusBadge}</td>
|
|
<td>${log.description || ""}</td>
|
|
`;
|
|
|
|
// Add click handler for details modal - whole row is clickable
|
|
row.addEventListener("click", () => {
|
|
const url = `/logs/${log.id}/detail`;
|
|
this.logModal.loadAndShow(url, "Error loading log details.");
|
|
});
|
|
|
|
this.logsTableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
createCategoryBadge(category) {
|
|
const categoryColors = {
|
|
gui_interaction: "bg-primary",
|
|
config_change: "bg-warning",
|
|
scraper_command: "bg-info",
|
|
scraper_activity: "bg-success",
|
|
system: "bg-danger",
|
|
data_import: "bg-secondary",
|
|
};
|
|
|
|
const colorClass = categoryColors[category] || "bg-secondary";
|
|
const displayName = category
|
|
.replace(/_/g, " ")
|
|
.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
|
|
return `<span class="badge ${colorClass}">${displayName}</span>`;
|
|
}
|
|
|
|
updatePagination(pagination) {
|
|
if (!pagination || !this.paginationContainer) return;
|
|
|
|
this.currentPage = pagination.page;
|
|
this.totalPages = pagination.pages;
|
|
this.totalEntries = pagination.total;
|
|
|
|
// Show pagination container
|
|
this.paginationContainer.classList.remove("d-none");
|
|
|
|
// Update pagination info
|
|
const startEntry = (pagination.page - 1) * pagination.per_page + 1;
|
|
const endEntry = Math.min(
|
|
pagination.page * pagination.per_page,
|
|
pagination.total
|
|
);
|
|
|
|
if (this.paginationInfo) {
|
|
this.paginationInfo.textContent = `Showing ${startEntry} - ${endEntry} of ${pagination.total} entries`;
|
|
}
|
|
|
|
// Update current page display
|
|
if (this.currentPageSpan) {
|
|
this.currentPageSpan.innerHTML = `<span class="page-link">${pagination.page} of ${pagination.pages}</span>`;
|
|
}
|
|
|
|
// Update previous button
|
|
if (this.prevPageBtn) {
|
|
if (pagination.has_prev) {
|
|
this.prevPageBtn.classList.remove("disabled");
|
|
this.prevPageBtn.querySelector("a").removeAttribute("tabindex");
|
|
this.prevPageBtn
|
|
.querySelector("a")
|
|
.setAttribute("aria-disabled", "false");
|
|
} else {
|
|
this.prevPageBtn.classList.add("disabled");
|
|
this.prevPageBtn.querySelector("a").setAttribute("tabindex", "-1");
|
|
this.prevPageBtn
|
|
.querySelector("a")
|
|
.setAttribute("aria-disabled", "true");
|
|
}
|
|
}
|
|
|
|
// Update next button
|
|
if (this.nextPageBtn) {
|
|
if (pagination.has_next) {
|
|
this.nextPageBtn.classList.remove("disabled");
|
|
this.nextPageBtn.querySelector("a").removeAttribute("tabindex");
|
|
this.nextPageBtn
|
|
.querySelector("a")
|
|
.setAttribute("aria-disabled", "false");
|
|
} else {
|
|
this.nextPageBtn.classList.add("disabled");
|
|
this.nextPageBtn.querySelector("a").setAttribute("tabindex", "-1");
|
|
this.nextPageBtn
|
|
.querySelector("a")
|
|
.setAttribute("aria-disabled", "true");
|
|
}
|
|
}
|
|
}
|
|
|
|
hidePagination() {
|
|
if (this.paginationContainer) {
|
|
this.paginationContainer.classList.add("d-none");
|
|
}
|
|
}
|
|
|
|
updateUrl() {
|
|
// Update URL with current filters for bookmarking
|
|
const params = new URLSearchParams();
|
|
|
|
Object.entries(this.filters).forEach(([key, value]) => {
|
|
if (value) {
|
|
if (key === "category" && Array.isArray(value)) {
|
|
// Handle multiple categories
|
|
value.forEach((cat) => {
|
|
if (cat) params.append("category", cat);
|
|
});
|
|
} else if (value) {
|
|
params.append(key, value);
|
|
}
|
|
}
|
|
});
|
|
|
|
const newUrl = `${window.location.pathname}${
|
|
params.toString() ? "?" + params.toString() : ""
|
|
}`;
|
|
window.history.replaceState({}, "", newUrl);
|
|
}
|
|
|
|
downloadLogs() {
|
|
// Build download URL with current filters
|
|
const params = new URLSearchParams();
|
|
|
|
Object.entries(this.filters).forEach(([key, value]) => {
|
|
if (value) {
|
|
if (key === "category" && Array.isArray(value)) {
|
|
// Handle multiple categories
|
|
value.forEach((cat) => {
|
|
if (cat) params.append("category", cat);
|
|
});
|
|
} else if (value) {
|
|
params.append(key, value);
|
|
}
|
|
}
|
|
});
|
|
|
|
const downloadUrl = `/logs/download${
|
|
params.toString() ? "?" + params.toString() : ""
|
|
}`;
|
|
window.location.href = downloadUrl;
|
|
}
|
|
|
|
refresh() {
|
|
this.loadLogs();
|
|
}
|
|
|
|
/**
|
|
* Set modal handler for log details
|
|
* @param {ModalHandler} modalHandler - Modal handler instance
|
|
*/
|
|
setModalHandler(modalHandler) {
|
|
this.logModal = modalHandler;
|
|
}
|
|
}
|
|
|
|
// Export for use in other modules
|
|
if (typeof window !== "undefined") {
|
|
window.LoggerManager = LoggerManager;
|
|
}
|