CPUIntel Core i7-8700K @ 3.70GHz (6C/12T)
GPUNVIDIA RTX 2080 (4GB) + Intel UHD 630
@@ -506,14 +509,60 @@ async function updateDocker() {
}
}
+// โโโ Windows Gaming PC Live Stats โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+async function updateWindowsStats() {
+ const el = document.getElementById("win-pc-metrics");
+ if (!el) return;
+ try {
+ const r = await fetch("/api/windows/stats");
+ const d = await r.json();
+ if (!d.online) {
+ el.innerHTML = `
Windows PC offline`;
+ return;
+ }
+ const memPct = d.mem_total_gb > 0 ? Math.round((d.mem_used_gb / d.mem_total_gb) * 100) : 0;
+ const diskPct = d.disk_c_total_gb > 0 ? Math.round((d.disk_c_used_gb / d.disk_c_total_gb) * 100) : 0;
+
+ el.innerHTML = `
+
+
+
RAM
+
+
${d.mem_used_gb}/${d.mem_total_gb} GB
+
+
+
C:
+
+
${d.disk_c_used_gb}/${d.disk_c_total_gb} GB
+
+
+ Uptime: ${d.uptime_days}d · Win 11 Pro
+
`;
+ } catch(e) {
+ el.innerHTML = `
Windows stats unavailable`;
+ }
+}
+
// โโโ Init โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
renderServices();
updateStatus();
updateHosts();
updateDocker();
+updateWindowsStats();
setInterval(updateStatus, 60000);
setInterval(updateHosts, 30000);
setInterval(updateDocker, 30000);
+setInterval(updateWindowsStats, 30000);
// โโโ Dark Mode Toggle โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function toggleDark() {
diff --git a/serve.py b/serve.py
index b4c8d4e..9a9b426 100755
--- a/serve.py
+++ b/serve.py
@@ -241,6 +241,49 @@ async def docker_stats():
return {"error": str(e)[:200], "containers": []}
+@app.get("/api/windows/stats")
+async def windows_stats():
+ """Get live stats from Windows Gaming PC via PowerShell over SSH."""
+ import subprocess, base64, json as _json
+
+ # PowerShell script: backslash counter paths need single \, Python raw string preserves them
+ ps_script = r'''
+$cpu=(Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples[0].CookedValue
+$os=Get-CimInstance Win32_OperatingSystem
+$disk=Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'"
+$uptime=(Get-Date)-$os.LastBootUpTime
+@{
+ 'cpu'=[math]::Round($cpu,1)
+ 'mem_total_gb'=[math]::Round($os.TotalVisibleMemorySize/1MB,1)
+ 'mem_free_gb'=[math]::Round($os.FreePhysicalMemory/1MB,1)
+ 'disk_c_total_gb'=[math]::Round($disk.Size/1GB,1)
+ 'disk_c_free_gb'=[math]::Round($disk.FreeSpace/1GB,1)
+ 'uptime_days'=[math]::Round($uptime.TotalDays,1)
+} | ConvertTo-Json
+'''.strip()
+
+ try:
+ encoded = base64.b64encode(ps_script.encode('utf-16le')).decode()
+ result = subprocess.run(
+ ["ssh", "gamingpc",
+ "powershell", "-NoProfile", "-EncodedCommand", encoded],
+ capture_output=True, text=True, timeout=30
+ )
+ if result.returncode != 0:
+ return {"error": result.stderr.strip()[:300], "online": False}
+
+ # Filter CLIXML noise from stdout
+ clean = "\n".join(l for l in result.stdout.split("\n")
+ if l.strip() and not l.startswith("#<") and not l.startswith("<"))
+ data = _json.loads(clean)
+ data["online"] = True
+ data["mem_used_gb"] = round(data["mem_total_gb"] - data["mem_free_gb"], 1)
+ data["disk_c_used_gb"] = round(data["disk_c_total_gb"] - data["disk_c_free_gb"], 1)
+ return data
+ except Exception as e:
+ return {"error": str(e)[:300], "online": False}
+
+
@app.get("/{path:path}")
async def index(path: str = ""):
"""Serve static files, fallback to index.html."""