152 lines
6.3 KiB
JavaScript
152 lines
6.3 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 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) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
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:", error);
|
|
logTableContainer.innerHTML = '<p class="loading-message">Error loading logs. Please check server connection.</p>';
|
|
}
|
|
}
|
|
|
|
function renderLogTable(logs) {
|
|
console.log('Rendering logs:', logs.length);
|
|
logTableContainer.innerHTML = '';
|
|
|
|
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}</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;
|
|
}
|
|
|
|
applyFiltersButton.addEventListener('click', () => {
|
|
const selectedRadio = document.querySelector('input[name="log-level"]:checked');
|
|
currentLevel = selectedRadio ? selectedRadio.value : '';
|
|
const sinceValue = sinceFilter.value;
|
|
// Convert local datetime input to ISO string for backend, handling potential timezone issues
|
|
currentSince = sinceValue ? new Date(sinceValue).toISOString().replace(/\.\d{3}Z$/, 'Z') : ''; // Ensure 'Z' for UTC
|
|
console.log('Applying filters:', { level: currentLevel, since: currentSince });
|
|
fetchLogs();
|
|
});
|
|
|
|
console.log('Initializing logs page');
|
|
fetchLogs();
|
|
setInterval(fetchLogs, POLLING_INTERVAL_MS);
|
|
});
|