135 lines
6.4 KiB
JavaScript
135 lines
6.4 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const nodeGridContainer = document.getElementById('node-grid-container');
|
|
const nodeCountSpan = document.getElementById('node-count');
|
|
const POLLING_INTERVAL_MS = 3000; // Poll every 3 seconds
|
|
|
|
async function fetchNodeData() {
|
|
try {
|
|
// Use window.API_BASE_PATH for dynamic base URL
|
|
const response = await fetch(`${window.API_BASE_PATH}/nodes/status`);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const data = await response.json();
|
|
renderNodeGrid(data.nodes);
|
|
} catch (error) {
|
|
console.error("Error fetching node data:", error);
|
|
nodeGridContainer.innerHTML = '<p class="loading-message">Error loading node data. Please check server connection.</p>';
|
|
}
|
|
}
|
|
|
|
function renderNodeGrid(nodes) {
|
|
nodeGridContainer.innerHTML = ''; // Clear existing content
|
|
nodeCountSpan.textContent = nodes.length; // Update total node count
|
|
|
|
if (nodes.length === 0) {
|
|
nodeGridContainer.innerHTML = '<p class="loading-message">No nodes reporting yet. Start a client!</p>';
|
|
return;
|
|
}
|
|
|
|
// Create a dynamic grid container
|
|
const grid = document.createElement('div');
|
|
grid.classList.add('connection-grid');
|
|
// Dynamically set grid columns based on number of nodes + 1 for the header column
|
|
// minmax(100px, 1fr) for the row header, then repeat for each node column
|
|
grid.style.gridTemplateColumns = `minmax(100px, 1fr) repeat(${nodes.length}, minmax(100px, 1fr))`;
|
|
|
|
// Add header row for column UUIDs
|
|
const headerRow = document.createElement('div');
|
|
headerRow.classList.add('grid-row', 'header-row');
|
|
headerRow.appendChild(createEmptyCell()); // Top-left corner
|
|
nodes.forEach(node => {
|
|
const headerCell = document.createElement('div');
|
|
headerCell.classList.add('grid-cell', 'header-cell');
|
|
const displayUuid = node.uuid.substring(0, 8) + '...';
|
|
headerCell.innerHTML = `<div class="node-uuid" title="${node.uuid}">${displayUuid}</div>`;
|
|
headerRow.appendChild(headerCell);
|
|
});
|
|
grid.appendChild(headerRow);
|
|
|
|
// Create rows for the grid
|
|
nodes.forEach((rowNode, rowIndex) => {
|
|
const row = document.createElement('div');
|
|
row.classList.add('grid-row');
|
|
|
|
// Add row header (UUID)
|
|
const rowHeader = document.createElement('div');
|
|
rowHeader.classList.add('grid-cell', 'header-cell');
|
|
const displayUuid = rowNode.uuid.substring(0, 8) + '...';
|
|
rowHeader.innerHTML = `<div class="node-uuid" title="${rowNode.uuid}">${displayUuid}</div>`;
|
|
row.appendChild(rowHeader);
|
|
|
|
// Add cells for connections
|
|
nodes.forEach((colNode, colIndex) => {
|
|
const cell = document.createElement('div');
|
|
cell.classList.add('grid-cell');
|
|
if (rowIndex === colIndex) {
|
|
// Diagonal: show node health status
|
|
cell.classList.add(`node-${rowNode.health_status}`);
|
|
cell.innerHTML = `
|
|
<div class="node-status-text">Status: ${rowNode.health_status.toUpperCase()}</div>
|
|
<div class="node-tooltip">
|
|
<p><strong>UUID:</strong> ${rowNode.uuid}</p>
|
|
<p><strong>IP:</strong> ${rowNode.ip}</p>
|
|
<p><strong>Last Seen:</strong> ${new Date(rowNode.last_seen).toLocaleTimeString()}</p>
|
|
<p><strong>Uptime:</strong> ${rowNode.uptime_seconds ? formatUptime(rowNode.uptime_seconds) : 'N/A'}</p>
|
|
<p><strong>Load Avg (1m, 5m, 15m):</strong> ${rowNode.load_avg ? rowNode.load_avg.map(l => l.toFixed(2)).join(', ') : 'N/A'}</p>
|
|
<p><strong>Memory Usage:</strong> ${rowNode.memory_usage_percent ? rowNode.memory_usage_percent.toFixed(2) + '%' : 'N/A'}</p>
|
|
</div>
|
|
`;
|
|
} else {
|
|
// Off-diagonal: show ping latency
|
|
const latency = rowNode.connections && colNode.uuid in rowNode.connections && rowNode.connections[colNode.uuid] !== null ? rowNode.connections[colNode.uuid] : null;
|
|
const displayLatency = latency !== null && !isNaN(latency) ? `${latency.toFixed(1)} ms` : 'N/A';
|
|
const latencyClass = latency !== null && !isNaN(latency) ? getLatencyClass(latency) : 'latency-unavailable';
|
|
cell.classList.add(latencyClass);
|
|
cell.innerHTML = `
|
|
<div class="conn-status-text">Ping: ${displayLatency}</div>
|
|
<div class="node-tooltip">
|
|
<p><strong>From:</strong> ${rowNode.uuid.substring(0, 8)}...</p>
|
|
<p><strong>to:</strong> ${colNode.uuid.substring(0, 8)}...</p>
|
|
<p><strong>Latency:</strong> ${displayLatency}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
row.appendChild(cell);
|
|
});
|
|
grid.appendChild(row);
|
|
});
|
|
|
|
nodeGridContainer.appendChild(grid);
|
|
}
|
|
|
|
function getLatencyClass(latency) {
|
|
if (latency <= 200) return 'latency-low';
|
|
if (latency <= 1000) return 'latency-medium';
|
|
return 'latency-high';
|
|
}
|
|
|
|
function createEmptyCell() {
|
|
const cell = document.createElement('div');
|
|
cell.classList.add('grid-cell', 'empty-cell');
|
|
return cell;
|
|
}
|
|
|
|
function formatUptime(seconds) {
|
|
const days = Math.floor(seconds / (3600 * 24));
|
|
seconds %= (3600 * 24);
|
|
const hours = Math.floor(seconds / 3600);
|
|
seconds %= 3600;
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remainingSeconds = seconds % 60;
|
|
|
|
let parts = [];
|
|
if (days > 0) parts.push(`${days}d`);
|
|
if (hours > 0) parts.push(`${hours}h`);
|
|
if (minutes > 0) parts.push(`${minutes}m`);
|
|
if (remainingSeconds > 0 || parts.length === 0) parts.push(`${remainingSeconds}s`);
|
|
return parts.join(' ');
|
|
}
|
|
|
|
// Initial fetch and then set up polling
|
|
fetchNodeData();
|
|
setInterval(fetchNodeData, POLLING_INTERVAL_MS);
|
|
});
|