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 %}
-
-
-
+
+
+
-
-
+
+
+
+
-
-
-
- Clear
-
-
- Wrap
-
+
+
+
+ Hide
-
+
- Loaded: {{ loaded_count|default(0) }} /
Total: {{ total_logs|default(0) }} logs |
- Matches: 0
+ Loaded: {{ loaded_count|default(0) }} |
+ Displayed: 0
-
-
Loading logs...
+
- Page 1 of 1
+ Page 1 / 1
-
-
- Previous
+
+
+ Prev
-
+
Next
-
- Load All
+
+ All