const nodesTable = document.getElementById('nodesTable'); const messagesList = document.getElementById('messagesList'); const statusDot = document.getElementById('statusDot'); const statusText = document.getElementById('statusText'); const nodeCountBadge = document.getElementById('nodeCountBadge'); let nodes = {}; let ws; function connectWebSocket() { const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; ws = new WebSocket(`${proto}//${location.host}/ws`); ws.onopen = () => { statusDot.classList.add('connected'); statusText.textContent = 'Verbunden'; }; ws.onclose = () => { statusDot.classList.remove('connected'); statusText.textContent = 'Getrennt - Reconnect...'; setTimeout(connectWebSocket, 3000); }; ws.onerror = () => { ws.close(); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); switch (msg.type) { case 'initial': msg.data.forEach(node => { nodes[node.node_id] = node; }); renderNodes(); break; case 'node_update': nodes[msg.data.node_id] = msg.data; renderNodes(); break; case 'new_message': addMessage(msg.data); break; case 'stats_update': updateStats(msg.data); break; } }; } function renderNodes() { const sorted = Object.values(nodes).sort((a, b) => (b.last_seen || 0) - (a.last_seen || 0)); nodeCountBadge.textContent = sorted.length; nodesTable.innerHTML = sorted.map(node => { const name = node.long_name || node.short_name || node.node_id; const shortId = node.node_id ? node.node_id.slice(-4) : '?'; const hw = node.hw_model || '-'; const snr = node.snr != null ? `${node.snr.toFixed(1)} dB` : '-'; const battery = renderBattery(node.battery); const lastSeen = node.last_seen ? timeAgo(node.last_seen) : '-'; const onlineClass = isOnline(node.last_seen) ? 'text-success' : ''; return `
${escapeHtml(shortId)}