document.addEventListener('DOMContentLoaded', () => { const logTableContainer = document.getElementById('log-table-container'); const logCountSpan = document.getElementById('log-count'); const POLLING_INTERVAL_MS = 5000; // Poll every 5 seconds const serviceUuid = logTableContainer.dataset.serviceUuid || '{{ service_uuid }}'; // Fallback for non-dynamic rendering async function fetchLogs() { try { const response = await fetch(`/${serviceUuid}/logs?limit=100`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); renderLogTable(data.logs); logCountSpan.textContent = data.log_count; } catch (error) { console.error("Error fetching logs:", error); logTableContainer.innerHTML = '

Error loading logs. Please check server connection.

'; } } function renderLogTable(logs) { logTableContainer.innerHTML = ''; // Clear existing content if (logs.length === 0) { logTableContainer.innerHTML = '

No logs available.

'; return; } // Create table const table = document.createElement('table'); table.classList.add('log-table'); // Create header 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); // Create body const tbody = document.createElement('tbody'); logs.forEach(log => { const row = document.createElement('tr'); row.innerHTML = ` ${new Date(log.timestamp).toLocaleString()} ${log.level} ${escapeHtml(log.message)} ${log.extra ? `
Show JSON
` : '-'} `; tbody.appendChild(row); }); table.appendChild(tbody); logTableContainer.appendChild(table); // Add click handlers for JSON toggles 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; } // Initial fetch and polling fetchLogs(); setInterval(fetchLogs, POLLING_INTERVAL_MS); });