/** * 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 = '
Loading...
Loading logs...'; // 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 = 'Error loading logs. Please try again.'; this.hidePagination(); } } renderLogs(logs) { if (!this.logsTableBody) return; this.logsTableBody.innerHTML = ""; if (!logs || logs.length === 0) { this.logsTableBody.innerHTML = 'No logs found matching the current filters.'; 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 = ` ${timeStr} ${categoryBadge} ${log.action} ${statusBadge} ${log.description || ""} `; // 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 `${displayName}`; } 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 = `${pagination.page} of ${pagination.pages}`; } // 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; }