diff --git a/static/js/logs.js b/static/js/logs.js index 9b574c2..85cb358 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -1,61 +1,94 @@ /** * HAProxy Logs Management - * Pagination and dynamic loading of logs + * 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; - loadLogs(); + applyFilter(); }); refreshBtn.addEventListener('click', function() { currentPage = 1; + filterRegex = null; + searchFilter.value = ''; loadLogs(); }); prevBtn.addEventListener('click', function() { if (currentPage > 1) { currentPage--; - loadLogs(); + applyFilter(); } }); nextBtn.addEventListener('click', function() { - const totalPages = Math.ceil(totalLogs / perPage); + const filtered = filterRegex ? allLoadedLogs.filter(log => filterRegex.test(log)) : allLoadedLogs; + const totalPages = Math.ceil(filtered.length / perPage); if (currentPage < totalPages) { currentPage++; - loadLogs(); + applyFilter(); } }); loadAllBtn.addEventListener('click', function() { perPage = totalLogs; currentPage = 1; - if (perPageSelect.querySelector(`option[value="${totalLogs}"]`)) { - perPageSelect.value = totalLogs; - } - loadLogs(); + perPageSelect.value = totalLogs; + applyFilter(); }); /** - * Load logs from API with pagination + * Load logs from API */ function loadLogs() { console.log(`[Logs] Loading page ${currentPage} with ${perPage} per page`); @@ -73,8 +106,10 @@ document.addEventListener('DOMContentLoaded', function() { .then(response => response.json()) .then(data => { if (data.success) { - renderLogs(data.logs); + 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'); @@ -87,60 +122,92 @@ document.addEventListener('DOMContentLoaded', function() { } /** - * Render logs in the table + * Apply filter and display logs */ - function renderLogs(logs) { - if (!logs || logs.length === 0) { - logsContainer.innerHTML = ' No logs available'; - return; + 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 } - logsContainer.innerHTML = logs.map(entry => ` - - - - ${escapeHtml(entry.timestamp || 'N/A')}
- ${escapeHtml(entry.source || 'N/A')}
- - ${escapeHtml(entry.message || 'N/A')} - -
- - - `).join(''); + 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; } /** - * Update pagination controls + * 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); - loadedSpan.textContent = data.logs.length; currentPageSpan.textContent = data.page; totalPagesSpan.textContent = totalPages; - - // Disable/enable navigation buttons - prevBtn.disabled = data.page === 1; - nextBtn.disabled = !data.has_more; } /** * Show error message */ function showError(message) { - logsContainer.innerHTML = ` - - -
- ${escapeHtml(message)} -
- - - `; + logsContainer.textContent = `ERROR: ${escapeHtml(message)}`; } /** - * Escape HTML to prevent XSS + * Escape HTML */ function escapeHtml(text) { const map = { diff --git a/templates/logs.html b/templates/logs.html index b2bb2f9..6b6064f 100644 --- a/templates/logs.html +++ b/templates/logs.html @@ -27,58 +27,51 @@ {% endif %} - -
-
- - +
+
+
+ +
-
-
+
+
+ + +
+
-
+
Loaded: {{ loaded_count|default(0) }} / - Total: {{ total_logs|default(0) }} logs + Total: {{ total_logs|default(0) }} logs | + Matches: 0
- -
- - - {% if logs %} - {% for entry in logs %} - - - - {% endfor %} - {% else %} - - - - {% endif %} - -
- - {{ entry.get('timestamp', 'N/A') }}
- {{ entry.get('source', 'N/A') }}
- - {{ entry.get('message', 'N/A') }} - -
-
- No logs available -
+ +
+
Loading logs...