/** * 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("logFiltersForm"); this.categorySelect = document.getElementById("category"); this.statusSelect = document.getElementById("status"); this.startDateInput = document.getElementById("start_date"); this.endDateInput = document.getElementById("end_date"); this.searchTermInput = document.getElementById("search_term"); 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("logPageSize"); // Pagination elements this.paginationContainer = document.getElementById("logsPagination"); this.paginationInfo = document.getElementById("logsPaginationInfo"); this.prevPageBtn = document.getElementById("logsPrevPage"); this.nextPageBtn = document.getElementById("logsNextPage"); this.currentPageSpan = document.getElementById("logsCurrentPage"); // 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(); }); } // Individual filter changes for immediate application [ this.categorySelect, 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(); } }); } } applyInitialFilters() { // Set form values from initial filters if (this.categorySelect && this.initialFilters.category) { this.categorySelect.value = this.initialFilters.category; } 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 this.filters = { category: this.categorySelect?.value || "", 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 form fields if (this.categorySelect) this.categorySelect.value = ""; 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 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) { 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-item"; 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 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) { 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) { 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; }