/** * HAProxy Logs Management * Pagination, filtering, and formatting of logs */ document.addEventListener('DOMContentLoaded', function() { let currentPage = 1; let perPage = 50; let totalLogs = parseInt(document.getElementById('total_count').textContent); let filterRegex = null; let wrapEnabled = false; let allLoadedLogs = []; const logsContainer = document.getElementById('logs_container'); const searchFilter = document.getElementById('search_filter'); const perPageSelect = document.getElementById('logs_per_page'); const refreshBtn = document.getElementById('refresh_logs_btn'); const prevBtn = document.getElementById('prev_btn'); const nextBtn = document.getElementById('next_btn'); const loadAllBtn = document.getElementById('load_all_btn'); const clearFilterBtn = document.getElementById('clear_filter_btn'); const toggleWrapBtn = document.getElementById('toggle_wrap_btn'); const loadedSpan = document.getElementById('loaded_count'); const matchSpan = document.getElementById('match_count'); const currentPageSpan = document.getElementById('current_page'); const totalPagesSpan = document.getElementById('total_pages'); const logsWrapper = document.getElementById('logs_container_wrapper'); // Event Listeners searchFilter.addEventListener('keyup', function() { currentPage = 1; try { filterRegex = new RegExp(this.value, 'gi'); } catch(e) { filterRegex = null; } applyFilter(); }); clearFilterBtn.addEventListener('click', function() { searchFilter.value = ''; filterRegex = null; currentPage = 1; applyFilter(); }); toggleWrapBtn.addEventListener('click', function() { wrapEnabled = !wrapEnabled; const pre = document.getElementById('logs_container'); pre.style.whiteSpace = wrapEnabled ? 'pre-wrap' : 'pre'; toggleWrapBtn.classList.toggle('active', wrapEnabled); }); perPageSelect.addEventListener('change', function(e) { perPage = parseInt(e.target.value); currentPage = 1; applyFilter(); }); refreshBtn.addEventListener('click', function() { currentPage = 1; filterRegex = null; searchFilter.value = ''; loadLogs(); }); prevBtn.addEventListener('click', function() { if (currentPage > 1) { currentPage--; applyFilter(); } }); nextBtn.addEventListener('click', function() { const filtered = filterRegex ? allLoadedLogs.filter(log => filterRegex.test(log)) : allLoadedLogs; const totalPages = Math.ceil(filtered.length / perPage); if (currentPage < totalPages) { currentPage++; applyFilter(); } }); loadAllBtn.addEventListener('click', function() { perPage = totalLogs; currentPage = 1; perPageSelect.value = totalLogs; applyFilter(); }); /** * Load logs from API */ function loadLogs() { console.log(`[Logs] Loading page ${currentPage} with ${perPage} per page`); fetch('/api/logs', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ page: currentPage, per_page: perPage }) }) .then(response => response.json()) .then(data => { if (data.success) { allLoadedLogs = data.logs; loadedSpan.textContent = data.logs.length; updatePagination(data); applyFilter(); console.log(`[Logs] Successfully loaded page ${data.page}/${Math.ceil(data.total / data.per_page)}`); } else { showError(data.error || 'Unknown error'); } }) .catch(error => { console.error('[Logs] Error loading logs:', error); showError('Failed to load logs. Please try again.'); }); } /** * Apply filter and display logs */ function applyFilter() { let filtered = allLoadedLogs; if (filterRegex) { filtered = allLoadedLogs.filter(log => { const fullLog = `${log.timestamp || ''} ${log.source || ''} ${log.message || ''}`; return filterRegex.test(fullLog); }); filterRegex.lastIndex = 0; // Reset regex for next test } matchSpan.textContent = filtered.length; const totalPages = Math.ceil(filtered.length / perPage) || 1; totalPagesSpan.textContent = totalPages; const offset = (currentPage - 1) * perPage; const paginated = filtered.slice(offset, offset + perPage); renderLogs(paginated, filtered); prevBtn.disabled = currentPage === 1; nextBtn.disabled = offset + perPage >= filtered.length; } /** * Render logs with syntax highlighting */ function renderLogs(logs, allFiltered) { if (!logs || logs.length === 0) { logsContainer.textContent = '(No logs available)'; return; } const output = logs.map((entry, idx) => { const timestamp = entry.timestamp || 'N/A'; const source = entry.source || 'N/A'; const message = entry.message || 'N/A'; // Format with colors let formatted = ''; // Timestamp (blue) formatted += `\x1b[36m${timestamp}\x1b[0m `; // Source IP (yellow) formatted += `\x1b[33m${source}\x1b[0m `; // Message (white) formatted += `${message}`; return `${idx + 1}. ${formatted}`; }).join('\n'); // Convert ANSI-like colors to HTML-like (for better compatibility) logsContainer.textContent = output; // Highlight search matches if filter is active if (filterRegex) { const text = logsContainer.textContent; const highlighted = text.replace(filterRegex, match => `>>> ${match} <<<`); logsContainer.textContent = highlighted; filterRegex.lastIndex = 0; } } /** * Update pagination info */ function updatePagination(data) { const totalPages = Math.ceil(data.total / data.per_page); currentPageSpan.textContent = data.page; totalPagesSpan.textContent = totalPages; } /** * Show error message */ function showError(message) { logsContainer.textContent = `ERROR: ${escapeHtml(message)}`; } /** * Escape HTML */ function escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, m => map[m]); } // Initial load loadLogs(); });