/** * HAProxy Logs Management with Security Alerts * Fixed pagination */ document.addEventListener('DOMContentLoaded', function() { let currentPage = 1; let perPage = 50; let totalLogs = parseInt(document.getElementById('total_count').textContent); let allLoadedLogs = []; let excludePhrases = []; const logsContainer = document.getElementById('logs_container'); const searchFilter = document.getElementById('search_filter'); const excludeFilter = document.getElementById('exclude_filter'); const excludeBtn = document.getElementById('exclude_btn'); 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 loadedSpan = document.getElementById('loaded_count'); const matchSpan = document.getElementById('match_count'); const currentPageSpan = document.getElementById('current_page'); const totalPagesSpan = document.getElementById('total_pages'); // Event Listeners searchFilter.addEventListener('keyup', debounce(function() { console.log('[Logs] Search changed'); currentPage = 1; loadLogsWithPage(); }, 300)); excludeBtn.addEventListener('click', function() { const phrase = excludeFilter.value.trim(); if (phrase) { if (!excludePhrases.includes(phrase)) { excludePhrases.push(phrase); updateExcludeUI(); currentPage = 1; loadLogsWithPage(); } excludeFilter.value = ''; } }); excludeFilter.addEventListener('keypress', function(e) { if (e.key === 'Enter') excludeBtn.click(); }); clearFilterBtn.addEventListener('click', function() { console.log('[Logs] Clear filters'); searchFilter.value = ''; excludePhrases = []; excludeFilter.value = ''; updateExcludeUI(); currentPage = 1; loadLogsWithPage(); }); perPageSelect.addEventListener('change', function() { console.log(`[Logs] Per page changed to ${this.value}`); perPage = parseInt(this.value); currentPage = 1; loadLogsWithPage(); }); refreshBtn.addEventListener('click', function() { console.log('[Logs] Refresh clicked'); searchFilter.value = ''; excludePhrases = []; excludeFilter.value = ''; updateExcludeUI(); currentPage = 1; loadLogsWithPage(); }); prevBtn.addEventListener('click', function() { if (currentPage > 1) { console.log(`[Logs] Prev button: page ${currentPage} -> ${currentPage - 1}`); currentPage--; loadLogsWithPage(); } }); nextBtn.addEventListener('click', function() { const totalPages = parseInt(document.getElementById('total_pages').textContent); if (currentPage < totalPages) { console.log(`[Logs] Next button: page ${currentPage} -> ${currentPage + 1}`); currentPage++; loadLogsWithPage(); } }); loadAllBtn.addEventListener('click', function() { console.log('[Logs] Load all clicked'); perPage = totalLogs > 500 ? 500 : totalLogs; currentPage = 1; perPageSelect.value = perPage; loadLogsWithPage(); }); /** * Debounce function */ function debounce(func, wait) { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(func, wait); }; } /** * Load logs with pagination from API */ function loadLogsWithPage() { console.log(`[Logs] loadLogsWithPage: page=${currentPage}, per_page=${perPage}, search="${searchFilter.value.trim()}", exclude=${excludePhrases.length}`); logsContainer.innerHTML = 'Loading logs...'; fetch('/api/logs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ page: currentPage, per_page: perPage, search: searchFilter.value.trim(), exclude: excludePhrases }) }) .then(r => r.json()) .then(data => { console.log('[Logs] API Response:', data); if (data.success) { allLoadedLogs = data.logs; loadedSpan.textContent = data.loaded_count; totalLogs = data.total; document.getElementById('total_count').textContent = data.total; const totalPages = Math.ceil(data.total_filtered / perPage) || 1; totalPagesSpan.textContent = totalPages; matchSpan.textContent = data.total_filtered; currentPageSpan.textContent = data.page; renderLogs(data.logs); // Update button states prevBtn.disabled = currentPage === 1; nextBtn.disabled = !data.has_more; console.log(`[Logs] Updated: page ${data.page}/${totalPages}, has_more=${data.has_more}, prev_disabled=${prevBtn.disabled}, next_disabled=${nextBtn.disabled}`); } else { showError(data.error); } }) .catch(e => { console.error('[Logs] Error:', e); showError('Failed to load logs: ' + e.message); }); } /** * Render logs as table rows */ function renderLogs(logs) { if (!logs || logs.length === 0) { logsContainer.innerHTML = 'No logs found'; return; } logsContainer.innerHTML = logs.map((entry) => { const threat_badges = []; if (entry.xss_alert) threat_badges.push('XSS'); if (entry.sql_alert) threat_badges.push('SQL'); if (entry.webshell_alert) threat_badges.push('SHELL'); if (entry.put_method) threat_badges.push('PUT'); if (entry.illegal_resource) threat_badges.push('403'); const threat_html = threat_badges.length > 0 ? `
${threat_badges.join('')}
` : ''; let row_class = ''; if (entry.has_threat) { row_class = 'table-danger'; } else if (entry.status_code.startsWith('5')) { row_class = 'table-danger'; } else if (entry.status_code.startsWith('4')) { row_class = 'table-warning'; } else if (entry.status_code.startsWith('2')) { row_class = 'table-light'; } else { row_class = 'table-light'; } return ` ${threat_html} ${escapeHtml(entry.timestamp)}
${escapeHtml(entry.ip_address)} ${escapeHtml(entry.http_method)} ${escapeHtml(entry.requested_url)} ${escapeHtml(entry.status_code)}
${escapeHtml(entry.frontend)}~ ${escapeHtml(entry.backend)} `; }).join(''); } /** * Update exclude UI */ function updateExcludeUI() { if (excludePhrases.length > 0) { const tags = excludePhrases.map((phrase, idx) => ` ${escapeHtml(phrase)} `).join(''); const container = document.createElement('div'); container.className = 'small mt-2'; container.innerHTML = `Hiding: ${tags}`; const existing = document.getElementById('exclude_ui'); if (existing) existing.remove(); container.id = 'exclude_ui'; excludeFilter.parentElement.parentElement.after(container); } else { const existing = document.getElementById('exclude_ui'); if (existing) existing.remove(); } } /** * Remove exclude phrase */ window.removeExcludePhrase = function(idx) { console.log(`[Logs] Remove exclude phrase at index ${idx}`); excludePhrases.splice(idx, 1); updateExcludeUI(); currentPage = 1; loadLogsWithPage(); }; /** * Show error */ function showError(msg) { logsContainer.innerHTML = `${escapeHtml(msg)}`; } /** * Escape HTML */ function escapeHtml(text) { const map = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}; return (text || '').replace(/[&<>"']/g, m => map[m]); } // Initial load console.log('[Logs] Initial load'); loadLogsWithPage(); });