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 POLLING_INTERVAL_MS = 5000; const serviceUuid = logTableContainer.dataset.serviceUuid; // Get service UUID from data attribute let currentLevel = ''; let currentSince = ''; 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); if (!response.ok) { const errorText = await response.text(); // Try to get response body as text console.error('Response text on error:', errorText); // Log it // If the server returns a 404, it might be due to a stale UUID. // Log a more specific message. 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 = `

Error: Service UUID mismatch. Please hard refresh your browser tab (Ctrl+F5 or Cmd+Shift+R).

`; } else { // Log the status code to help debug console.error(`HTTP error! status: ${response.status} - ${errorText}`); logTableContainer.innerHTML = `

Error loading logs. HTTP Status: ${response.status}. Please check server connection or hard refresh.

`; } return; // Stop further processing if error } 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 = '

Error loading logs. Failed to parse response or other network issue. See console for details.

'; } } function renderLogTable(logs) { console.log('Rendering logs:', logs.length); logTableContainer.innerHTML = ''; if (logs.length === 0) { logTableContainer.innerHTML = '

No logs available.

'; 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 = ` ${new Date(log.timestamp).toLocaleString()} ${log.level} ${escapeHtml(log.message)} ${log.extra ? `
Show JSON
` : '-'} `; 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; } applyFiltersButton.addEventListener('click', () => { const selectedRadio = document.querySelector('input[name="log-level"]:checked'); currentLevel = selectedRadio ? selectedRadio.value : ''; // Send the datetime-local value directly. It's already in YYYY-MM-DDTHH:mm (24-hour) format. // The backend will interpret this as UTC. currentSince = sinceFilter.value; console.log('Applying filters:', { level: currentLevel, since: currentSince }); fetchLogs(); }); console.log('Initializing logs page'); fetchLogs(); setInterval(fetchLogs, POLLING_INTERVAL_MS); });