From 3e7861f489cbc12e1e97de522e12e1a385d25a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Tue, 4 Nov 2025 08:47:17 +0100 Subject: [PATCH] fixes --- static/js/logs.js | 230 ++++++++++++++++++++++++++------------------ templates/logs.html | 67 +++++++------ 2 files changed, 172 insertions(+), 125 deletions(-) diff --git a/static/js/logs.js b/static/js/logs.js index 85cb358..a12af37 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -1,82 +1,89 @@ /** * HAProxy Logs Management - * Pagination, filtering, and formatting of logs + * Pagination, filtering, and proper formatting */ 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 = []; + 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 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() { + searchFilter.addEventListener('keyup', debounce(function() { currentPage = 1; - try { - filterRegex = new RegExp(this.value, 'gi'); - } catch(e) { - filterRegex = null; + applyFilters(); + }, 300)); + + excludeBtn.addEventListener('click', function() { + const phrase = excludeFilter.value.trim(); + if (phrase) { + if (!excludePhrases.includes(phrase)) { + excludePhrases.push(phrase); + updateExcludeUI(); + applyFilters(); + } + excludeFilter.value = ''; } - applyFilter(); + }); + + excludeFilter.addEventListener('keypress', function(e) { + if (e.key === 'Enter') excludeBtn.click(); }); clearFilterBtn.addEventListener('click', function() { searchFilter.value = ''; - filterRegex = null; + excludePhrases = []; + excludeFilter.value = ''; + updateExcludeUI(); currentPage = 1; - applyFilter(); + applyFilters(); }); - 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); + perPageSelect.addEventListener('change', function() { + perPage = parseInt(this.value); currentPage = 1; - applyFilter(); + applyFilters(); }); refreshBtn.addEventListener('click', function() { - currentPage = 1; - filterRegex = null; searchFilter.value = ''; + excludePhrases = []; + excludeFilter.value = ''; + updateExcludeUI(); + currentPage = 1; loadLogs(); }); prevBtn.addEventListener('click', function() { if (currentPage > 1) { currentPage--; - applyFilter(); + applyFilters(); } }); nextBtn.addEventListener('click', function() { - const filtered = filterRegex ? allLoadedLogs.filter(log => filterRegex.test(log)) : allLoadedLogs; + const filtered = getFilteredLogs(); const totalPages = Math.ceil(filtered.length / perPage); if (currentPage < totalPages) { currentPage++; - applyFilter(); + applyFilters(); } }); @@ -84,142 +91,177 @@ document.addEventListener('DOMContentLoaded', function() { perPage = totalLogs; currentPage = 1; perPageSelect.value = totalLogs; - applyFilter(); + applyFilters(); }); + /** + * Debounce function + */ + function debounce(func, wait) { + let timeout; + return function() { + clearTimeout(timeout); + timeout = setTimeout(func, wait); + }; + } + /** * Load logs from API */ function loadLogs() { - console.log(`[Logs] Loading page ${currentPage} with ${perPage} per page`); + logsContainer.innerHTML = 'Loading logs...'; fetch('/api/logs', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ page: currentPage, per_page: perPage }) }) - .then(response => response.json()) + .then(r => r.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)}`); + applyFilters(); } else { - showError(data.error || 'Unknown error'); + showError(data.error); } }) - .catch(error => { - console.error('[Logs] Error loading logs:', error); - showError('Failed to load logs. Please try again.'); + .catch(e => { + console.error('Error:', e); + showError('Failed to load logs'); }); } /** - * Apply filter and display logs + * Get filtered logs */ - function applyFilter() { + function getFilteredLogs() { let filtered = allLoadedLogs; - if (filterRegex) { - filtered = allLoadedLogs.filter(log => { - const fullLog = `${log.timestamp || ''} ${log.source || ''} ${log.message || ''}`; - return filterRegex.test(fullLog); + // Apply search filter + if (searchFilter.value.trim()) { + const query = searchFilter.value.toLowerCase(); + filtered = filtered.filter(log => { + const text = `${log.timestamp || ''} ${log.source || ''} ${log.message || ''}`.toLowerCase(); + return text.includes(query); }); - filterRegex.lastIndex = 0; // Reset regex for next test } + // Apply exclude phrases + if (excludePhrases.length > 0) { + filtered = filtered.filter(log => { + const text = `${log.timestamp || ''} ${log.source || ''} ${log.message || ''}`; + return !excludePhrases.some(phrase => text.includes(phrase)); + }); + } + + return filtered; + } + + /** + * Apply all filters and render + */ + function applyFilters() { + const filtered = getFilteredLogs(); matchSpan.textContent = filtered.length; const totalPages = Math.ceil(filtered.length / perPage) || 1; totalPagesSpan.textContent = totalPages; + currentPageSpan.textContent = currentPage; const offset = (currentPage - 1) * perPage; const paginated = filtered.slice(offset, offset + perPage); - renderLogs(paginated, filtered); + renderLogs(paginated); prevBtn.disabled = currentPage === 1; nextBtn.disabled = offset + perPage >= filtered.length; } /** - * Render logs with syntax highlighting + * Render logs as table rows */ - function renderLogs(logs, allFiltered) { + function renderLogs(logs) { if (!logs || logs.length === 0) { - logsContainer.textContent = '(No logs available)'; + logsContainer.innerHTML = 'No logs matching criteria'; 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'; + logsContainer.innerHTML = logs.map((entry, idx) => { + const timestamp = entry.timestamp || '-'; + const source = entry.source || '-'; + const message = entry.message || '-'; - // Format with colors - let formatted = ''; + // Color code by status code if present + let statusClass = ''; + if (message.includes('200')) statusClass = 'table-success'; + else if (message.includes('404')) statusClass = 'table-warning'; + else if (message.includes('500')) statusClass = 'table-danger'; - // Timestamp (blue) - formatted += `\x1b[36m${timestamp}\x1b[0m `; + return ` + + + ${escapeHtml(timestamp)}
+ ${escapeHtml(source)}
+ ${escapeHtml(message)} + + + `; + }).join(''); + } + + /** + * Update exclude UI to show active filters + */ + function updateExcludeUI() { + if (excludePhrases.length > 0) { + const tags = excludePhrases.map((phrase, idx) => ` + + ${escapeHtml(phrase)} + + `).join(''); - // Source IP (yellow) - formatted += `\x1b[33m${source}\x1b[0m `; + const container = document.createElement('div'); + container.className = 'small mt-2'; + container.innerHTML = `Hiding: ${tags}`; - // Message (white) - formatted += `${message}`; + const existing = document.getElementById('exclude_ui'); + if (existing) existing.remove(); - 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; + container.id = 'exclude_ui'; + excludeFilter.parentElement.parentElement.after(container); + } else { + const existing = document.getElementById('exclude_ui'); + if (existing) existing.remove(); } } /** - * Update pagination info + * Global function to remove exclude phrase */ - function updatePagination(data) { - const totalPages = Math.ceil(data.total / data.per_page); - currentPageSpan.textContent = data.page; - totalPagesSpan.textContent = totalPages; - } + window.removeExcludePhrase = function(idx) { + excludePhrases.splice(idx, 1); + updateExcludeUI(); + applyFilters(); + }; /** - * Show error message + * Show error */ - function showError(message) { - logsContainer.textContent = `ERROR: ${escapeHtml(message)}`; + function showError(msg) { + logsContainer.innerHTML = `${escapeHtml(msg)}`; } /** * Escape HTML */ function escapeHtml(text) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return text.replace(/[&<>"']/g, m => map[m]); + const map = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}; + return (text || '').replace(/[&<>"']/g, m => map[m]); } - // Initial load loadLogs(); }); diff --git a/templates/logs.html b/templates/logs.html index 6b6064f..372667b 100644 --- a/templates/logs.html +++ b/templates/logs.html @@ -27,67 +27,72 @@ {% endif %} - -
-
+ +
+
- +
-
- +
+ +
+
-
-
- - +
+ + +
-
+
- Loaded: {{ loaded_count|default(0) }} / Total: {{ total_logs|default(0) }} logs | - Matches: 0 + Loaded: {{ loaded_count|default(0) }} | + Displayed: 0
-
-
Loading logs...
+
+ + + + +
Loading logs...
- Page 1 of 1 + Page 1 / 1 -
- - -