';
}
// -----------------------------------------------------------------------
// Memory Card
// -----------------------------------------------------------------------
function renderMemoryCard(m) {
var el = document.getElementById('sysCardMemory');
if (!el) return;
var mem = m.memory;
if (!mem) { el.innerHTML = '
';
}
// -----------------------------------------------------------------------
// Temperature & Power Card
// -----------------------------------------------------------------------
function _extractPrimaryTemp(temps) {
if (!temps) return null;
var preferred = ['cpu_thermal', 'coretemp', 'k10temp', 'acpitz', 'soc_thermal'];
for (var i = 0; i < preferred.length; i++) {
if (temps[preferred[i]] && temps[preferred[i]].length) return temps[preferred[i]][0];
}
for (var key in temps) {
if (temps[key] && temps[key].length) return temps[key][0];
}
return null;
}
function renderTempCard(m) {
var el = document.getElementById('sysCardTemp');
if (!el) return;
var temp = _extractPrimaryTemp(m.temperatures);
var html = '
Temperature & Power
';
if (temp) {
// Update sparkline history
tempHistory.push(temp.current);
if (tempHistory.length > SPARKLINE_SIZE) tempHistory.shift();
html += '
' + Math.round(temp.current) + '°C
';
html += '
' + sparklineSvg(tempHistory) + '
';
// Additional sensors
if (m.temperatures) {
for (var chip in m.temperatures) {
m.temperatures[chip].forEach(function (s) {
html += '
';
});
}
}
} else {
html += 'No temperature sensors';
}
// Fans
if (m.fans) {
for (var fChip in m.fans) {
m.fans[fChip].forEach(function (f) {
html += '
';
}
// Throttle flags (Pi)
if (m.power && m.power.throttled) {
html += '
Throttle: 0x' + m.power.throttled + '
';
}
// Power draw
if (m.power && m.power.draw_watts != null) {
html += '
Power: ' + m.power.draw_watts + ' W
';
}
html += '
';
el.innerHTML = html;
}
// -----------------------------------------------------------------------
// Disk Card
// -----------------------------------------------------------------------
function renderDiskCard(m) {
var el = document.getElementById('sysCardDisk');
if (!el) return;
var disk = m.disk;
if (!disk) { el.innerHTML = '
';
}
}
if (m.disk_io) {
prevDiskIo = m.disk_io;
prevDiskTimestamp = m.timestamp;
}
html += '
';
el.innerHTML = html;
}
// -----------------------------------------------------------------------
// Network Card
// -----------------------------------------------------------------------
function renderNetworkCard(m) {
var el = document.getElementById('sysCardNetwork');
if (!el) return;
var net = m.network;
if (!net) { el.innerHTML = '
Network
N/A
'; return; }
var html = '
Network
';
// Interfaces
var ifaces = net.interfaces || [];
if (ifaces.length === 0) {
html += 'No interfaces';
} else {
ifaces.forEach(function (iface) {
html += '
';
var details = [];
if (iface.mac) details.push('MAC: ' + iface.mac);
if (iface.speed) details.push(iface.speed + ' Mbps');
if (details.length) html += '
' + escHtml(details.join(' | ')) + '
';
// Bandwidth for this interface
if (net.io && net.io[iface.name] && prevNetIo && prevNetIo[iface.name] && prevNetTimestamp) {
var dt = (m.timestamp - prevNetTimestamp);
if (dt > 0) {
var prev = prevNetIo[iface.name];
var cur = net.io[iface.name];
var upRate = (cur.bytes_sent - prev.bytes_sent) / dt;
var downRate = (cur.bytes_recv - prev.bytes_recv) / dt;
html += '
';
});
}
// Connection count
if (net.connections != null) {
html += '
Connections: ' + net.connections + '
';
}
// Save for next delta
if (net.io) {
prevNetIo = net.io;
prevNetTimestamp = m.timestamp;
}
html += '
';
el.innerHTML = html;
}
// -----------------------------------------------------------------------
// Location & Weather Card
// -----------------------------------------------------------------------
function renderLocationCard() {
var el = document.getElementById('sysCardLocation');
if (!el) return;
var html = '
Location & Weather
';
html += '
';
// Globe container
html += '';
// Details column
html += '
';
if (locationData && locationData.lat != null) {
html += '
';
el.innerHTML = html;
}
// -----------------------------------------------------------------------
// Process Card
// -----------------------------------------------------------------------
function renderProcessCard(m) {
var el = document.getElementById('sysCardProcesses');
if (!el) return;
var procs = m.processes || {};
var keys = Object.keys(procs).sort();
var html = '
Active Processes
';
if (!keys.length) {
html += 'No data';
} else {
var running = 0, stopped = 0;
html += '
';
keys.forEach(function (k) {
var isRunning = procs[k];
if (isRunning) running++; else stopped++;
var dotCls = isRunning ? 'running' : 'stopped';
var label = k.charAt(0).toUpperCase() + k.slice(1);
html += '
' +
' ' +
'' + escHtml(label) + '' +
'
';
});
html += '
';
html += '
' + running + ' running / ' + stopped + ' idle
';
}
html += '
';
el.innerHTML = html;
}
// -----------------------------------------------------------------------
// System Info Card
// -----------------------------------------------------------------------
function renderSystemInfoCard(m) {
var el = document.getElementById('sysCardInfo');
if (!el) return;
var sys = m.system || {};
var html = '
System Info
';
html += '
Host: ' + escHtml(sys.hostname || '--') + '
';
html += '
OS: ' + escHtml(sys.platform || '--') + '
';
html += '
Python: ' + escHtml(sys.python || '--') + '
';
html += '
App: v' + escHtml(sys.version || '--') + '
';
html += '
Uptime: ' + escHtml(sys.uptime_human || '--') + '
';
if (m.boot_time) {
var bootDate = new Date(m.boot_time * 1000);
html += '
Boot: ' + escHtml(bootDate.toUTCString()) + '
';
}
if (m.network && m.network.connections != null) {
html += '
Connections: ' + m.network.connections + '
';
}
html += '
';
el.innerHTML = html;
}
// -----------------------------------------------------------------------
// Sidebar Updates
// -----------------------------------------------------------------------
function updateSidebarQuickStats(m) {
var cpuEl = document.getElementById('sysQuickCpu');
var tempEl = document.getElementById('sysQuickTemp');
var ramEl = document.getElementById('sysQuickRam');
var diskEl = document.getElementById('sysQuickDisk');
if (cpuEl) cpuEl.textContent = m.cpu ? Math.round(m.cpu.percent) + '%' : '--';
if (ramEl) ramEl.textContent = m.memory ? Math.round(m.memory.percent) + '%' : '--';
if (diskEl) diskEl.textContent = m.disk ? Math.round(m.disk.percent) + '%' : '--';
var temp = _extractPrimaryTemp(m.temperatures);
if (tempEl) tempEl.innerHTML = temp ? Math.round(temp.current) + '°C' : '--';
// Color-code values
[cpuEl, ramEl, diskEl].forEach(function (el) {
if (!el) return;
var val = parseInt(el.textContent);
el.classList.remove('sys-val-ok', 'sys-val-warn', 'sys-val-crit');
if (!isNaN(val)) el.classList.add('sys-val-' + barClass(val));
});
}
function updateSidebarProcesses(m) {
var el = document.getElementById('sysProcessList');
if (!el) return;
var procs = m.processes || {};
var keys = Object.keys(procs).sort();
if (!keys.length) { el.textContent = 'No data'; return; }
var running = keys.filter(function (k) { return procs[k]; });
var stopped = keys.filter(function (k) { return !procs[k]; });
el.innerHTML =
(running.length ? '' + running.length + ' running' : '') +
(running.length && stopped.length ? ' · ' : '') +
(stopped.length ? '' + stopped.length + ' stopped' : '');
}
function updateSidebarNetwork(m) {
var el = document.getElementById('sysQuickNet');
if (!el || !m.network) return;
var ifaces = m.network.interfaces || [];
var ips = [];
ifaces.forEach(function (iface) {
if (iface.ipv4 && iface.is_up) {
ips.push(iface.name + ': ' + iface.ipv4);
}
});
el.textContent = ips.length ? ips.join(', ') : '--';
}
function updateSidebarBattery(m) {
var section = document.getElementById('sysQuickBatterySection');
var el = document.getElementById('sysQuickBattery');
if (!section || !el) return;
if (m.battery) {
section.style.display = '';
el.textContent = Math.round(m.battery.percent) + '%' + (m.battery.plugged ? ' (plugged)' : '');
} else {
section.style.display = 'none';
}
}
function updateSidebarLocation() {
var el = document.getElementById('sysQuickLocation');
if (!el) return;
if (locationData && locationData.lat != null) {
el.textContent = locationData.lat.toFixed(4) + ', ' + locationData.lon.toFixed(4) + ' (' + locationData.source + ')';
} else {
el.textContent = 'No location';
}
}
// -----------------------------------------------------------------------
// Render all
// -----------------------------------------------------------------------
function renderAll(m) {
renderCpuCard(m);
renderMemoryCard(m);
renderTempCard(m);
renderDiskCard(m);
renderNetworkCard(m);
renderProcessCard(m);
renderSystemInfoCard(m);
updateSidebarQuickStats(m);
updateSidebarProcesses(m);
updateSidebarNetwork(m);
updateSidebarBattery(m);
}
// -----------------------------------------------------------------------
// Location & Weather Fetching
// -----------------------------------------------------------------------
function fetchLocation() {
fetch('/system/location')
.then(function (r) { return r.json(); })
.then(function (data) {
locationData = data;
updateSidebarLocation();
renderLocationCard();
if (data.lat != null) fetchWeather();
})
.catch(function () {
renderLocationCard();
});
}
function fetchWeather() {
if (!locationData || locationData.lat == null) return;
fetch('/system/weather?lat=' + locationData.lat + '&lon=' + locationData.lon)
.then(function (r) { return r.json(); })
.then(function (data) {
weatherData = data;
renderLocationCard();
})
.catch(function () {});
}
// -----------------------------------------------------------------------
// SSE Connection
// -----------------------------------------------------------------------
function connect() {
if (eventSource) return;
eventSource = new EventSource('/system/stream');
eventSource.onmessage = function (e) {
try {
var data = JSON.parse(e.data);
if (data.type === 'keepalive') return;
lastMetrics = data;
renderAll(data);
} catch (_) { /* ignore parse errors */ }
};
eventSource.onopen = function () {
connected = true;
};
eventSource.onerror = function () {
connected = false;
};
}
function disconnect() {
if (eventSource) {
eventSource.close();
eventSource = null;
}
connected = false;
}
// -----------------------------------------------------------------------
// SDR Devices
// -----------------------------------------------------------------------
function refreshSdr() {
var sidebarEl = document.getElementById('sysSdrList');
if (sidebarEl) sidebarEl.innerHTML = 'Scanning…';
var cardEl = document.getElementById('sysCardSdr');
if (cardEl) cardEl.innerHTML = '
SDR Devices
Scanning…
';
fetch('/system/sdr_devices')
.then(function (r) { return r.json(); })
.then(function (data) {
var devices = data.devices || [];
renderSdrCard(devices);
// Update sidebar
if (sidebarEl) {
if (!devices.length) {
sidebarEl.innerHTML = 'No SDR devices found';
} else {
var html = '';
devices.forEach(function (d) {
html += '