Home Lab System Dashboard v 2.0

Server · Hyperion · Sentinel · Legacy — live system metrics
Last update: — Refresh interval: 5s
Server
192.168.8.2 · Fedora Server 43 (Headless)
OK
CPU
RAM
Disk
System
Uptime
Load avg
Net TX
Net RX
Temperatures
Hyperion
192.168.8.25 · Pop!_OS
OK
CPU
RAM
GPU
Disk
System
Uptime
Load avg
Net TX
Net RX
Temperatures
Sentinel
192.168.8.20 · Raspberry Pi‑5
OK
CPU
RAM
Disk
System
Uptime
Load avg
Net TX
Net RX
Temperatures
Legacy-Reborn
192.168.8.21 · Debian 12
OK
CPU
RAM
Disk
System
Uptime
Load avg
Net TX
Net RX
Temperatures
Data source: /opt/sysmetrics/sysmetrics.py for Sentinel
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import json, time, os, psutil, subprocess, platform

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        data = {
            "cpu_percent": psutil.cpu_percent(interval=0.1),
            "ram_used": psutil.virtual_memory().used,
            "ram_total": psutil.virtual_memory().total,
            "disk_used": psutil.disk_usage('/').used,
            "disk_total": psutil.disk_usage('/').total,
            "uptime": time.time() - psutil.boot_time(),
            "load_avg": os.getloadavg(),
            "net_io": psutil.net_io_counters()._asdict(),
            "temps": self.clean_temps(),
            "gpu": self.get_gpu_stats(),
            "specs": self.get_specs()
        }
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def get_specs(self):
        specs = {
            "cpu": self.get_cpu_model(),
            "ram_gb": round(psutil.virtual_memory().total / (1024**3)),
            "os": platform.system(),
            "gpu": self.get_gpu_model()
        }
        return specs

    def get_cpu_model(self):
        try:
            with open('/proc/cpuinfo', 'r') as f:
                lines = f.readlines()
                for line in lines:
                    if 'model name' in line.lower():
                        return line.split(':')[1].strip()
                for line in lines:
                    if 'Model' in line and 'Raspberry Pi' in line:
                        model = line.split(':')[1].strip()
                        try:
                            import subprocess
                            result = subprocess.run(['lscpu'], capture_output=True, text=True)
                            for lscpu_line in result.stdout.split('\n'):
                                if 'Model name' in lscpu_line:
                                    core = lscpu_line.split(':')[1].strip()
                                    return f"{model} ({core})"
                        except:
                            pass
                        return model
        except:
            pass
        return "Unknown CPU"

    def get_gpu_model(self):
        try:
            result = subprocess.run(
                ['nvidia-smi', '--query-gpu=name', '--format=csv,noheader'],
                capture_output=True, text=True, timeout=2
            )
            if result.returncode == 0:
                return result.stdout.strip()
        except:
            pass
        return None

    def get_gpu_stats(self):
        return None

    def clean_temps(self):
        raw_temps = psutil.sensors_temperatures() if hasattr(psutil, "sensors_temperatures") else {}
        cleaned = {}
        for sensor, readings in raw_temps.items():
            for reading in readings:
                label = reading[0] or sensor
                temp = reading[1]
                friendly_name = label
                if sensor not in cleaned:
                    cleaned[sensor] = []
                cleaned[sensor].append([friendly_name, temp, reading[2], reading[3]])
        return cleaned

server = HTTPServer(("0.0.0.0", 9000), Handler)
server.serve_forever()
Data source: /opt/sysmetrics/sysmetrics.py for Server, Hyperion, Legacy
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import json, time, os, psutil, subprocess, platform

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        data = {
            "cpu_percent": psutil.cpu_percent(interval=0.1),
            "ram_used": psutil.virtual_memory().used,
            "ram_total": psutil.virtual_memory().total,
            "disk_used": psutil.disk_usage('/').used,
            "disk_total": psutil.disk_usage('/').total,
            "uptime": time.time() - psutil.boot_time(),
            "load_avg": os.getloadavg(),
            "net_io": psutil.net_io_counters()._asdict(),
            "temps": self.clean_temps(),
            "gpu": self.get_gpu_stats(),
            "specs": self.get_specs()
        }
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def get_specs(self):
        specs = {
            "cpu": self.get_cpu_model(),
            "ram_gb": round(psutil.virtual_memory().total / (1024**3)),
            "os": platform.system(),
            "gpu": self.get_gpu_model()
        }
        return specs

    def get_cpu_model(self):
        try:
            with open('/proc/cpuinfo', 'r') as f:
                for line in f:
                    if 'model name' in line:
                        return line.split(':')[1].strip()
        except:
            pass
        return "Unknown CPU"

    def get_gpu_model(self):
        try:
            result = subprocess.run(
                ['nvidia-smi', '--query-gpu=name', '--format=csv,noheader'],
                capture_output=True, text=True, timeout=2
            )
            if result.returncode == 0:
                return result.stdout.strip()
        except:
            pass
        return None

    def get_gpu_stats(self):
        try:
            result = subprocess.run(
                ['nvidia-smi', '--query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu,fan.speed',
                 '--format=csv,noheader,nounits'],
                capture_output=True, text=True, timeout=2
            )
            if result.returncode == 0:
                parts = result.stdout.strip().split(',')
                return {
                    "util": float(parts[0].strip()),
                    "mem_used": float(parts[1].strip()) * 1024 * 1024,
                    "mem_total": float(parts[2].strip()) * 1024 * 1024,
                    "temp": float(parts[3].strip()),
                    "fan": float(parts[4].strip()) if parts[4].strip() != '[N/A]' else 0
                }
        except Exception:
            pass
        return None

    def clean_temps(self):
        raw_temps = psutil.sensors_temperatures() if hasattr(psutil, "sensors_temperatures") else {}
        cleaned = {}
        dimm_counter = 0
        for sensor, readings in raw_temps.items():
            for reading in readings:
                label = reading[0] or sensor
                temp = reading[1]
                friendly_name = label
                if sensor == 'coretemp':
                    if 'Package' in label:
                        friendly_name = 'CPU Package'
                    elif 'Core' in label:
                        continue
                    else:
                        continue
                elif 'r8169' in sensor:
                    friendly_name = 'Network Controller'
                elif sensor == 'nvme':
                    if 'Composite' in label:
                        friendly_name = 'NVMe Controller'
                    elif 'Sensor 2' in label:
                        friendly_name = 'NVMe NAND'
                    elif 'Sensor 1' in label:
                        continue
                elif sensor == 'acpitz':
                    friendly_name = 'Motherboard'
                elif sensor == 'spd5118':
                    dimm_counter += 1
                    friendly_name = f'RAM DIMM {dimm_counter}'
                elif sensor == 'k10temp':
                    if 'Tctl' in label:
                        friendly_name = 'CPU Package'
                    elif 'Tccd1' in label:
                        friendly_name = 'CPU CCD1'
                    elif 'Tccd2' in label:
                        friendly_name = 'CPU CCD2'
                elif sensor == 'jc42':
                    dimm_counter += 1
                    friendly_name = f'RAM DIMM {dimm_counter}'
                if sensor not in cleaned:
                    cleaned[sensor] = []
                cleaned[sensor].append([friendly_name, temp, reading[2], reading[3]])
        return cleaned

server = HTTPServer(("0.0.0.0", 9000), Handler)
server.serve_forever()