Переглянути джерело

feat(dashboard): replace interfaces, vlans, and vpn summary cards with live CPU & RAM usage telemetry

Proxmox Router 1 тиждень тому
батько
коміт
d769845bd5
2 змінених файлів з 161 додано та 61 видалено
  1. 120 22
      internal/web/static/app.js
  2. 41 39
      internal/web/static/index.html

+ 120 - 22
internal/web/static/app.js

@@ -252,32 +252,130 @@ function updateSystemMetrics() {
   const v = state.vpnStatus;
   if (!s) return;
 
-  // Sidebar
-  document.getElementById('side-hostname').innerText = s.hostname || 'debian-lxc';
-  document.getElementById('side-uptime').innerText = s.uptime_human || '0m';
-  document.getElementById('side-gateway-ip').innerText = s.default_gateway ? `GW: ${s.default_gateway}` : 'GW: Isolated';
-
+  // Sidebar Host & Uptime
+  const sideHost = document.getElementById('side-hostname');
+  if (sideHost) sideHost.innerText = s.hostname || 'debian-lxc';
+  const sideUp = document.getElementById('side-uptime');
+  if (sideUp) sideUp.innerText = s.uptime_human || '0m';
+  const sideGw = document.getElementById('side-gateway-ip');
+  if (sideGw) sideGw.innerText = s.default_gateway ? `GW: ${s.default_gateway}` : 'GW: Isolated';
+
+  // Compute CPU Metrics
+  let cpuPct = 0;
   if (s.cpu_pct !== undefined) {
-    const pct = s.cpu_pct;
-    document.getElementById('side-cpu-val').innerText = `${pct.toFixed(1)}%`;
-    document.getElementById('side-cpu-bar').style.width = `${Math.max(4, Math.min(100, pct))}%`;
-    const loadStr = s.cpu_load ? s.cpu_load.join(', ') : '-';
-    document.getElementById('side-cpu-bar').title = `Container: ${pct}% | Host Loadavg: ${loadStr} (${s.cpu_cores || 1} cores)`;
+    cpuPct = s.cpu_pct;
   } else if (s.cpu_load && s.cpu_load.length > 0) {
-    const cores = s.cpu_cores || 4;
-    const pct = Math.min(100, Math.round((parseFloat(s.cpu_load[0]) / cores) * 100));
-    document.getElementById('side-cpu-val').innerText = `${pct}%`;
-    document.getElementById('side-cpu-bar').style.width = `${Math.max(4, pct)}%`;
+    const cores = s.cpu_cores || 1;
+    cpuPct = Math.min(100, Math.round((parseFloat(s.cpu_load[0]) / cores) * 100));
+  }
+  const cpuPctStr = `${cpuPct.toFixed(1)}%`;
+  const cpuWidth = `${Math.max(3, Math.min(100, cpuPct))}%`;
+  const loadStr = s.cpu_load ? s.cpu_load.join(', ') : '-';
+
+  // Sidebar CPU
+  const sideCpuVal = document.getElementById('side-cpu-val');
+  if (sideCpuVal) sideCpuVal.innerText = cpuPctStr;
+  const sideCpuBar = document.getElementById('side-cpu-bar');
+  if (sideCpuBar) {
+    sideCpuBar.style.width = cpuWidth;
+    sideCpuBar.title = `Container: ${cpuPctStr} | Host Loadavg: ${loadStr} (${s.cpu_cores || 1} cores)`;
+  }
+
+  // Dashboard CPU Card
+  const dashCpuVal = document.getElementById('dash-cpu-val');
+  if (dashCpuVal) dashCpuVal.innerText = cpuPctStr;
+  const dashCpuBar = document.getElementById('dash-cpu-bar');
+  if (dashCpuBar) {
+    dashCpuBar.style.width = cpuWidth;
+    if (cpuPct > 85) {
+      dashCpuBar.className = 'h-full rounded-full bg-rose-500 transition-all duration-500';
+    } else if (cpuPct > 65) {
+      dashCpuBar.className = 'h-full rounded-full bg-amber-500 transition-all duration-500';
+    } else {
+      dashCpuBar.className = 'h-full rounded-full bg-emerald-500 transition-all duration-500';
+    }
+  }
+  const dashCpuCores = document.getElementById('dash-cpu-cores');
+  if (dashCpuCores) dashCpuCores.innerText = `${s.cpu_cores || 1} Cores`;
+  const dashCpuLoad = document.getElementById('dash-cpu-load');
+  if (dashCpuLoad) dashCpuLoad.innerText = loadStr;
+  const dashCpuStatus = document.getElementById('dash-cpu-status');
+  if (dashCpuStatus) {
+    if (cpuPct > 85) {
+      dashCpuStatus.innerText = 'High';
+      dashCpuStatus.className = 'text-[11px] font-semibold text-rose-400 uppercase px-2 py-0.5 rounded bg-rose-500/10';
+    } else if (cpuPct > 65) {
+      dashCpuStatus.innerText = 'Heavy';
+      dashCpuStatus.className = 'text-[11px] font-semibold text-amber-400 uppercase px-2 py-0.5 rounded bg-amber-500/10';
+    } else {
+      dashCpuStatus.innerText = 'Normal';
+      dashCpuStatus.className = 'text-[11px] font-semibold text-emerald-400 uppercase px-2 py-0.5 rounded bg-emerald-500/10';
+    }
+  }
+
+  // Compute RAM Metrics
+  const memPct = Math.round(s.mem_usage_pct || 0);
+  const memPctStr = `${memPct}%`;
+  const memWidth = `${Math.max(3, Math.min(100, memPct))}%`;
+  const usedMB = s.mem_used_mb || 0;
+  const totalMB = s.mem_total_mb || 0;
+  const freeMB = totalMB > usedMB ? totalMB - usedMB : 0;
+
+  function fmtMemMB(mb) {
+    if (mb >= 1024) {
+      return `${(mb / 1024).toFixed(1)} GB`;
+    }
+    return `${mb} MB`;
+  }
+
+  // Sidebar RAM
+  const sideRamVal = document.getElementById('side-ram-val');
+  if (sideRamVal) sideRamVal.innerText = memPctStr;
+  const sideRamBar = document.getElementById('side-ram-bar');
+  if (sideRamBar) sideRamBar.style.width = memWidth;
+
+  // Dashboard RAM Card
+  const dashRamVal = document.getElementById('dash-ram-val');
+  if (dashRamVal) dashRamVal.innerText = memPctStr;
+  const dashRamBar = document.getElementById('dash-ram-bar');
+  if (dashRamBar) {
+    dashRamBar.style.width = memWidth;
+    if (memPct > 85) {
+      dashRamBar.className = 'h-full rounded-full bg-rose-500 transition-all duration-500';
+    } else if (memPct > 70) {
+      dashRamBar.className = 'h-full rounded-full bg-amber-500 transition-all duration-500';
+    } else {
+      dashRamBar.className = 'h-full rounded-full bg-indigo-500 transition-all duration-500';
+    }
+  }
+  const dashRamUsedTotal = document.getElementById('dash-ram-used-total');
+  if (dashRamUsedTotal) {
+    dashRamUsedTotal.innerText = `${fmtMemMB(usedMB)} / ${fmtMemMB(totalMB)}`;
+  }
+  const dashRamFree = document.getElementById('dash-ram-free');
+  if (dashRamFree) {
+    dashRamFree.innerText = fmtMemMB(freeMB);
+  }
+  const dashRamStatus = document.getElementById('dash-ram-status');
+  if (dashRamStatus) {
+    if (memPct > 85) {
+      dashRamStatus.innerText = 'High';
+      dashRamStatus.className = 'text-[11px] font-semibold text-rose-400 uppercase px-2 py-0.5 rounded bg-rose-500/10';
+    } else {
+      dashRamStatus.innerText = 'Active';
+      dashRamStatus.className = 'text-[11px] font-semibold text-indigo-400 uppercase px-2 py-0.5 rounded bg-indigo-500/10';
+    }
   }
 
-  document.getElementById('side-ram-val').innerText = `${Math.round(s.mem_usage_pct || 0)}%`;
-  document.getElementById('side-ram-bar').style.width = `${Math.max(5, Math.round(s.mem_usage_pct || 0))}%`;
-
-  // Dashboard Stats
-  document.getElementById('stat-ifaces-up').innerText = s.up_ifaces || 0;
-  document.getElementById('stat-ifaces-total').innerText = `${s.total_ifaces || 0} Total`;
-  document.getElementById('stat-ifaces-down').innerText = `${s.down_ifaces || 0} DOWN`;
-  document.getElementById('stat-vlans-count').innerText = s.vlan_ifaces || 0;
+  // Legacy Dashboard Stats (Safe checks)
+  const elIfacesUp = document.getElementById('stat-ifaces-up');
+  if (elIfacesUp) elIfacesUp.innerText = s.up_ifaces || 0;
+  const elIfacesTot = document.getElementById('stat-ifaces-total');
+  if (elIfacesTot) elIfacesTot.innerText = `${s.total_ifaces || 0} Total`;
+  const elIfacesDown = document.getElementById('stat-ifaces-down');
+  if (elIfacesDown) elIfacesDown.innerText = `${s.down_ifaces || 0} DOWN`;
+  const elVlans = document.getElementById('stat-vlans-count');
+  if (elVlans) elVlans.innerText = s.vlan_ifaces || 0;
 
   // VPN Status Indicators
   if (v) {

+ 41 - 39
internal/web/static/index.html

@@ -354,63 +354,61 @@
 
         <!-- ================= SECTION: DASHBOARD ================= -->
         <section id="section-dashboard" class="space-y-6">
-          <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
+          <div class="grid grid-cols-1 md:grid-cols-3 gap-5">
 
+            <!-- CPU Usage -->
             <div class="bg-slate-900/80 border border-slate-800 rounded-xl p-5 relative overflow-hidden group hover:border-slate-700 transition">
               <div class="flex items-center justify-between mb-3">
-                <span class="text-xs font-semibold text-slate-400 uppercase tracking-wider">Interfaces</span>
-                <div class="p-2 bg-brand-500/10 text-brand-400 rounded-lg">
+                <span class="text-xs font-semibold text-slate-400 uppercase tracking-wider">CPU Usage</span>
+                <div class="p-2 bg-emerald-500/10 text-emerald-400 rounded-lg">
                   <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M3 9h2m-2 6h2m14-6h2m-2 6h2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
                   </svg>
                 </div>
               </div>
-              <div class="flex items-baseline space-x-2">
-                <span id="stat-ifaces-up" class="text-2xl font-bold text-white">0</span>
-                <span class="text-xs text-slate-400">UP /</span>
-                <span id="stat-ifaces-total" class="text-sm font-semibold text-slate-400">0 Total</span>
+              <div class="flex items-baseline justify-between">
+                <div class="flex items-baseline space-x-2">
+                  <span id="dash-cpu-val" class="text-2xl font-bold text-white">0.0%</span>
+                  <span id="dash-cpu-cores" class="text-xs text-slate-400 font-mono">0 Cores</span>
+                </div>
+                <span id="dash-cpu-status" class="text-[11px] font-semibold text-emerald-400 uppercase px-2 py-0.5 rounded bg-emerald-500/10">Normal</span>
+              </div>
+              <div class="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden my-2.5">
+                <div id="dash-cpu-bar" class="h-full rounded-full bg-emerald-500 transition-all duration-500" style="width: 0%"></div>
               </div>
-              <div class="mt-2 text-xs text-slate-400 flex items-center">
-                <span id="stat-ifaces-down" class="text-rose-400 font-medium mr-1.5">0 DOWN</span>
-                <span class="text-slate-500">•</span>
-                <span class="text-slate-400 ml-1.5">Plug & Play</span>
+              <div class="text-xs text-slate-400 flex items-center justify-between">
+                <span>Load: <span id="dash-cpu-load" class="font-mono text-slate-300">0.00, 0.00, 0.00</span></span>
+                <span class="text-[11px] text-slate-500 font-mono">Container vCPU</span>
               </div>
             </div>
 
+            <!-- RAM Usage -->
             <div class="bg-slate-900/80 border border-slate-800 rounded-xl p-5 relative overflow-hidden group hover:border-slate-700 transition">
               <div class="flex items-center justify-between mb-3">
-                <span class="text-xs font-semibold text-slate-400 uppercase tracking-wider">802.1Q VLANs</span>
-                <div class="p-2 bg-cyan-500/10 text-cyan-400 rounded-lg">
+                <span class="text-xs font-semibold text-slate-400 uppercase tracking-wider">RAM Usage</span>
+                <div class="p-2 bg-indigo-500/10 text-indigo-400 rounded-lg">
                   <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
                   </svg>
                 </div>
               </div>
-              <div class="flex items-baseline space-x-2">
-                <span id="stat-vlans-count" class="text-2xl font-bold text-cyan-400">0</span>
-                <span class="text-xs text-slate-400">Virtual Subnets</span>
-              </div>
-              <div class="mt-2 text-xs text-slate-400">Kernel Tagging Active</div>
-            </div>
-
-            <div class="bg-slate-900/80 border border-slate-800 rounded-xl p-5 relative overflow-hidden group hover:border-slate-700 transition">
-              <div class="flex items-center justify-between mb-3">
-                <span class="text-xs font-semibold text-slate-400 uppercase tracking-wider">WAN & VPN Services</span>
-                <div class="p-2 bg-violet-500/10 text-violet-400 rounded-lg">
-                  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
-                  </svg>
+              <div class="flex items-baseline justify-between">
+                <div class="flex items-baseline space-x-2">
+                  <span id="dash-ram-val" class="text-2xl font-bold text-white">0%</span>
+                  <span id="dash-ram-used-total" class="text-xs text-slate-400 font-mono">0 / 0 MB</span>
                 </div>
+                <span id="dash-ram-status" class="text-[11px] font-semibold text-indigo-400 uppercase px-2 py-0.5 rounded bg-indigo-500/10">Active</span>
               </div>
-              <div class="flex items-baseline space-x-2">
-                <span id="stat-vpn-active" class="text-2xl font-bold text-violet-400">0</span>
-                <span class="text-xs text-slate-400">Connected Tunnels</span>
+              <div class="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden my-2.5">
+                <div id="dash-ram-bar" class="h-full rounded-full bg-indigo-500 transition-all duration-500" style="width: 0%"></div>
               </div>
-              <div class="mt-2 text-[11px] text-slate-400 flex items-center gap-1.5 font-mono">
-                <span>WG • IPSec • PPPoE • L2TP</span>
+              <div class="text-xs text-slate-400 flex items-center justify-between">
+                <span>Free: <span id="dash-ram-free" class="font-mono text-slate-300">0 MB</span></span>
+                <span class="text-[11px] text-slate-500 font-mono">Dynamic Alloc</span>
               </div>
             </div>
 
+            <!-- Live Traffic -->
             <div class="bg-slate-900/80 border border-slate-800 rounded-xl p-5 relative overflow-hidden group hover:border-slate-700 transition">
               <div class="flex items-center justify-between mb-3">
                 <span class="text-xs font-semibold text-slate-400 uppercase tracking-wider">Live Traffic</span>
@@ -422,14 +420,18 @@
               </div>
               <div class="space-y-1">
                 <div class="flex items-center justify-between text-xs">
-                  <span class="text-slate-400 flex items-center gap-1"><span class="text-emerald-400 font-bold">↓</span> RX:</span>
-                  <span id="stat-traffic-rx" class="font-mono font-semibold text-emerald-400">0 B/s</span>
+                  <span class="text-slate-400 flex items-center gap-1.5"><span class="text-emerald-400 font-bold">↓</span> RX:</span>
+                  <span id="stat-traffic-rx" class="font-mono font-semibold text-emerald-400 text-sm">0 B/s</span>
                 </div>
                 <div class="flex items-center justify-between text-xs">
-                  <span class="text-slate-400 flex items-center gap-1"><span class="text-cyan-400 font-bold">↑</span> TX:</span>
-                  <span id="stat-traffic-tx" class="font-mono font-semibold text-cyan-400">0 B/s</span>
+                  <span class="text-slate-400 flex items-center gap-1.5"><span class="text-cyan-400 font-bold">↑</span> TX:</span>
+                  <span id="stat-traffic-tx" class="font-mono font-semibold text-cyan-400 text-sm">0 B/s</span>
                 </div>
               </div>
+              <div class="mt-2.5 pt-2 border-t border-slate-800/80 text-[11px] text-slate-500 flex items-center justify-between font-mono">
+                <span>Throughput</span>
+                <span class="text-emerald-400 font-semibold flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span> Streaming</span>
+              </div>
             </div>
 
           </div>
@@ -5200,6 +5202,6 @@
     </div>
   </div>
 
-  <script src="app.js?v=4.1"></script>
+  <script src="app.js?v=4.2"></script>
 </body>
 </html>