Files
node-monitor/app/web/static/logs.js
2025-06-14 01:19:57 +03:00

212 lines
9.1 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const logTableContainer = document.getElementById('log-table-container');
const logCountSpan = document.getElementById('log-count');
const levelRadios = document.querySelectorAll('input[name="log-level"]');
const sinceFilter = document.getElementById('since-filter');
const applyFiltersButton = document.getElementById('apply-filters');
const togglePollingButton = document.getElementById('toggle-polling');
const pollingStatusSpan = document.getElementById('polling-status');
const POLLING_INTERVAL_MS = 5000;
const serviceUuid = logTableContainer.dataset.serviceUuid; // Get service UUID from data attribute
let currentLevel = '';
let currentSince = '';
let pollingIntervalId = null;
let isPollingActive = true; // Start active by default
// Function to update the UI elements related to polling status
function updatePollingStatusUI() {
if (isPollingActive) {
togglePollingButton.textContent = 'Pause Auto-Refresh';
pollingStatusSpan.textContent = `Auto-refresh: ON (every ${POLLING_INTERVAL_MS / 1000} seconds)`;
} else {
togglePollingButton.textContent = 'Resume Auto-Refresh';
pollingStatusSpan.textContent = 'Auto-refresh: OFF (paused)';
}
}
// Function to start polling
function startPolling() {
if (pollingIntervalId) {
clearInterval(pollingIntervalId); // Clear any existing interval to prevent duplicates
}
isPollingActive = true;
fetchLogs(); // Fetch immediately when starting/resuming
pollingIntervalId = setInterval(fetchLogs, POLLING_INTERVAL_MS);
updatePollingStatusUI();
}
// Function to stop polling
function stopPolling() {
isPollingActive = false;
clearInterval(pollingIntervalId);
pollingIntervalId = null;
updatePollingStatusUI();
}
async function fetchLogs() {
console.log('Fetching logs with params:', { level: currentLevel, since: currentSince });
try {
const params = new URLSearchParams({
format: 'json',
limit: '100'
});
if (currentLevel) params.append('level', currentLevel);
if (currentSince) params.append('since', currentSince);
// Use window.API_BASE_PATH for dynamic base URL
const url = `${window.API_BASE_PATH}/${serviceUuid}/logs?${params.toString()}`;
console.log('Fetch URL:', url);
const response = await fetch(url);
console.log('Response status:', response.status);
console.log('Response Content-Type:', response.headers.get('Content-Type'));
if (!response.ok) {
const errorText = await response.text();
console.error('Raw response text on error:', errorText.substring(0, 500) + (errorText.length > 500 ? '...' : ''));
if (response.status === 404) {
console.error(`404 Not Found: Service UUID mismatch. Current page UUID: ${serviceUuid}. Server UUID might have changed. Please hard refresh the page.`);
logTableContainer.innerHTML = `<p class="loading-message error-message">Error: Service UUID mismatch. Please hard refresh your browser tab (Ctrl+F5 or Cmd+Shift+R).</p>`;
} else {
// Log the status code to help debug
console.error(`HTTP error! status: ${response.status} - ${errorText}`);
logTableContainer.innerHTML = `<p class="loading-message error-message">Error loading logs. HTTP Status: ${response.status}. Please check server connection or hard refresh.</p>`;
}
stopPolling(); // Stop polling on error
return;
}
// Attempt to parse JSON. This is where the error would occur if the content is HTML.
const data = await response.json();
console.log('Received logs:', data.logs.length);
renderLogTable(data.logs);
logCountSpan.textContent = data.log_count;
} catch (error) {
console.error("Error fetching logs (JSON parsing or other):", error);
logTableContainer.innerHTML = '<p class="loading-message error-message">Error loading logs. Failed to parse response or other network issue. See console for details.</p>';
stopPolling(); // Stop polling on error
}
}
function renderLogTable(logs) {
console.log('Rendering logs:', logs.length);
logTableContainer.innerHTML = ''; // Clear existing content before rendering
if (logs.length === 0) {
logTableContainer.innerHTML = '<p class="loading-message">No logs available.</p>';
return;
}
const table = document.createElement('table');
table.classList.add('log-table');
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
const headers = [
{ key: 'timestamp', label: 'Timestamp' },
{ key: 'level', label: 'Level' },
{ key: 'message', label: 'Message' },
{ key: 'extra', label: 'Extra' }
];
headers.forEach(header => {
const th = document.createElement('th');
th.textContent = header.label;
th.dataset.key = header.key;
th.classList.add('sortable');
th.addEventListener('click', () => sortTable(header.key));
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
const tbody = document.createElement('tbody');
logs.forEach(log => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${new Date(log.timestamp).toLocaleString()}</td>
<td class="log-level log-level-${(log.level || '').toLowerCase()}">${log.level || 'N/A'}</td>
<td>${escapeHtml(log.message)}</td>
<td>
${log.extra ? `
<div class="json-toggle">Show JSON</div>
<pre class="json-content" style="display: none;">${JSON.stringify(log.extra, null, 2)}</pre>
` : '-'}
</td>
`;
tbody.appendChild(row);
});
table.appendChild(tbody);
logTableContainer.appendChild(table);
document.querySelectorAll('.json-toggle').forEach(toggle => {
toggle.addEventListener('click', () => {
const jsonContent = toggle.nextElementSibling;
const isHidden = jsonContent.style.display === 'none';
jsonContent.style.display = isHidden ? 'block' : 'none';
toggle.textContent = isHidden ? 'Hide JSON' : 'Show JSON';
});
});
}
function sortTable(key) {
const table = document.querySelector('.log-table');
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.querySelectorAll('tr'));
const isAsc = table.dataset.sortKey === key && table.dataset.sortDir !== 'desc';
table.dataset.sortKey = key;
table.dataset.sortDir = isAsc ? 'desc' : 'asc';
rows.sort((a, b) => {
let aValue, bValue;
if (key === 'timestamp') {
aValue = new Date(a.cells[0].textContent);
bValue = new Date(b.cells[0].textContent);
} else if (key === 'level') {
aValue = a.cells[1].textContent.toLowerCase();
bValue = b.cells[1].textContent.toLowerCase();
} else if (key === 'message') {
aValue = a.cells[2].textContent.toLowerCase();
bValue = b.cells[2].textContent.toLowerCase();
} else {
aValue = a.cells[3].querySelector('.json-content')?.textContent || '';
bValue = b.cells[3].querySelector('.json-content')?.textContent || '';
}
return isAsc ? (aValue < bValue ? -1 : 1) : (aValue > bValue ? -1 : 1);
});
tbody.innerHTML = '';
rows.forEach(row => tbody.appendChild(row));
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// Event listener for the "Apply Filters" button
applyFiltersButton.addEventListener('click', () => {
const selectedRadio = document.querySelector('input[name="log-level"]:checked');
currentLevel = selectedRadio ? selectedRadio.value : '';
currentSince = sinceFilter.value;
console.log('Applying filters:', { level: currentLevel, since: currentSince });
// When filters are applied, always restart polling to fetch new data
startPolling();
});
// Event listener for the new "Toggle Polling" button
togglePollingButton.addEventListener('click', () => {
if (isPollingActive) {
stopPolling();
} else {
startPolling();
}
});
console.log('Initializing logs page');
// Initial call to start polling when the page loads
startPolling();
});