Upgrade NOC dashboard to V3 with latency and roadmap

This commit is contained in:
2026-04-25 17:45:59 -05:00
parent 0028475380
commit f03f0cb7de
5 changed files with 169 additions and 76 deletions
+34 -18
View File
@@ -1,69 +1,85 @@
{ {
"nocName": "Ced's NOC", "nocName": "Ced's NOC",
"status": "Operational Build", "status": "All Systems Online",
"lastUpdated": "2026-04-25 16:52:17", "lastUpdated": "2026-04-25 17:45:47",
"summary": { "summary": {
"nodes": "12+", "nodes": "12+",
"services": "7/7", "services": "7/7",
"online": "7",
"offline": "0",
"vlans": "4", "vlans": "4",
"edgeSystems": "2" "edgeSystems": "2"
}, },
"systems": [ "systems": [
{ {
"name": "Proxmox VE", "name": "Proxmox VE",
"type": "Virtualization", "type": "Virtualization Platform",
"address": "10.10.30.250:8006", "address": "10.10.30.250:8006",
"status": "Online", "status": "Online",
"layer": "Infrastructure", "layer": "Core Infrastructure",
"lastCheck": "2026-04-25 16:52:17" "label": "Hypervisor",
"latency": "4 ms",
"lastCheck": "2026-04-25 17:45:47"
}, },
{ {
"name": "TrueNAS", "name": "TrueNAS",
"type": "Storage", "type": "Storage Platform",
"address": "10.10.30.143", "address": "10.10.30.143",
"status": "Online", "status": "Online",
"layer": "Storage", "layer": "Storage",
"lastCheck": "2026-04-25 16:52:17" "label": "NAS",
"latency": "1 ms",
"lastCheck": "2026-04-25 17:45:47"
}, },
{ {
"name": "Nginx Proxy Manager", "name": "Nginx Proxy Manager",
"type": "Reverse Proxy", "type": "Reverse Proxy",
"address": "10.10.30.210:81", "address": "10.10.30.210:81",
"status": "Online", "status": "Online",
"layer": "Access", "layer": "Access Layer",
"lastCheck": "2026-04-25 16:52:17" "label": "Proxy",
"latency": "0 ms",
"lastCheck": "2026-04-25 17:45:47"
}, },
{ {
"name": "Dashy", "name": "Dashy",
"type": "Dashboard", "type": "Service Dashboard",
"address": "10.10.30.61:4000", "address": "10.10.30.61:4000",
"status": "Online", "status": "Online",
"layer": "Monitoring", "layer": "Dashboard",
"lastCheck": "2026-04-25 16:52:17" "label": "Portal",
"latency": "17 ms",
"lastCheck": "2026-04-25 17:45:47"
}, },
{ {
"name": "Jellyfin", "name": "Jellyfin",
"type": "Media Service", "type": "Media Service",
"address": "10.10.30.143:30013", "address": "10.10.30.143:30013",
"status": "Online", "status": "Online",
"layer": "Services", "layer": "Application Service",
"lastCheck": "2026-04-25 16:52:17" "label": "App",
"latency": "2 ms",
"lastCheck": "2026-04-25 17:45:47"
}, },
{ {
"name": "K3s API", "name": "K3s API",
"type": "Kubernetes API", "type": "Kubernetes API",
"address": "10.10.30.72:6443", "address": "10.10.30.72:6443",
"status": "Online", "status": "Online",
"layer": "Compute", "layer": "Compute Cluster",
"lastCheck": "2026-04-25 16:52:17" "label": "Cluster",
"latency": "13 ms",
"lastCheck": "2026-04-25 17:45:47"
}, },
{ {
"name": "APRS-IS", "name": "APRS-IS",
"type": "APRS Internet Service", "type": "APRS Internet Service",
"address": "noam.aprs2.net:14580", "address": "noam.aprs2.net:14580",
"status": "Online", "status": "Online",
"layer": "Edge", "layer": "Edge / RF",
"lastCheck": "2026-04-25 16:52:17" "label": "External",
"latency": "100 ms",
"lastCheck": "2026-04-25 17:45:47"
} }
] ]
} }
+22 -12
View File
@@ -155,19 +155,19 @@ building_real_systems</code></pre>
<div class="noc-metric"> <div class="noc-metric">
<h3 id="metric-services">--</h3> <h3 id="metric-services">--</h3>
<p>Services</p> <p>Services Online</p>
</div> </div>
<div class="noc-metric"> <div class="noc-metric">
<h3 id="metric-vlans">--</h3> <h3 id="metric-vlans">--</h3>
<p>VLANs</p> <p>Network Segments</p>
</div> </div>
<div class="noc-metric"> <div class="noc-metric">
<h3 id="metric-edge">--</h3> <h3 id="metric-edge">--</h3>
<p>Edge Systems</p> <p>Edge Systems</p>
</div> </div>
</div> </div>
<div id="noc-system-grid" class="noc-system-grid"></div> <div id="noc-system-grid" class="noc-system-grid"></div>
</div> </div>
@@ -318,31 +318,41 @@ building_real_systems</code></pre>
<section class="section noc-section"> <section class="section noc-section">
<div class="container"> <div class="container">
<div class="section-heading"> <div class="section-heading">
<p class="section-label">Future Buildout</p> <p class="section-label">NOC Roadmap</p>
<h2>📊 Ceds NOC Vision</h2> <h2>📊 Ceds NOC Vision</h2>
<p class="section-intro"> <p class="section-intro">
The long-term goal is to bring these systems together into a unified network operations view. Ceds NOC is being built as a staged monitoring and systems-visibility platform for HomeLab,
Kubernetes, edge devices, and future production-style dashboards.
</p> </p>
</div> </div>
<div class="noc-grid"> <div class="noc-grid">
<div class="noc-card"> <div class="noc-card">
<h3>Monitoring</h3> <h3>✅ Current: Portfolio NOC</h3>
<p>Grafana, Prometheus, Uptime Kuma, and service-level visibility.</p> <p>
JSON-driven dashboard that displays real inventory, online/offline checks, service labels,
latency, and last-check timestamps.
</p>
</div> </div>
<div class="noc-card"> <div class="noc-card">
<h3>Infrastructure</h3> <h3>⚙️ Next: Automated Data Updates</h3>
<p>HomeLab services, K3s nodes, VLANs, and self-hosted systems.</p> <p>
Scheduled local script updates will refresh system status data automatically and push dashboard-ready
JSON into the portfolio.
</p>
</div> </div>
<div class="noc-card"> <div class="noc-card">
<h3>Edge Systems</h3> <h3>📈 Future: Grafana + Prometheus</h3>
<p>APRS iGate nodes, Raspberry Pi devices, and field/mobile deployments.</p> <p>
Deeper monitoring stack for Proxmox, TrueNAS, K3s, Raspberry Pi nodes, Uptime Kuma,
and APRS edge systems.
</p>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<section id="resume" class="section"> <section id="resume" class="section">
<div class="container"> <div class="container">
+7
View File
@@ -55,11 +55,18 @@ async function loadNocStatus() {
<span class="mini-status ${statusClass}"></span> <span class="mini-status ${statusClass}"></span>
<span>${system.layer}</span> <span>${system.layer}</span>
</div> </div>
<div class="system-label">${system.label || "System"}</div>
<h3>${system.name}</h3> <h3>${system.name}</h3>
<p>${system.type}</p> <p>${system.type}</p>
<code>${system.address}</code> <code>${system.address}</code>
<div class="system-meta">
<small>Status: ${system.status}</small> <small>Status: ${system.status}</small>
<small>Latency: ${system.latency || "N/A"}</small>
<small>Last Check: ${system.lastCheck || "Manual"}</small> <small>Last Check: ${system.lastCheck || "Manual"}</small>
</div>
`; `;
grid.appendChild(card); grid.appendChild(card);
+46 -16
View File
@@ -1,5 +1,6 @@
import json import json
import socket import socket
import time
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@@ -8,19 +9,21 @@ OUTPUT_FILE = Path(__file__).resolve().parent.parent / "data" / "noc-status.json
SYSTEMS = [ SYSTEMS = [
{ {
"name": "Proxmox VE", "name": "Proxmox VE",
"type": "Virtualization", "type": "Virtualization Platform",
"host": "10.10.30.250", "host": "10.10.30.250",
"port": 8006, "port": 8006,
"address": "10.10.30.250:8006", "address": "10.10.30.250:8006",
"layer": "Infrastructure" "layer": "Core Infrastructure",
"label": "Hypervisor"
}, },
{ {
"name": "TrueNAS", "name": "TrueNAS",
"type": "Storage", "type": "Storage Platform",
"host": "10.10.30.143", "host": "10.10.30.143",
"port": 80, "port": 80,
"address": "10.10.30.143", "address": "10.10.30.143",
"layer": "Storage" "layer": "Storage",
"label": "NAS"
}, },
{ {
"name": "Nginx Proxy Manager", "name": "Nginx Proxy Manager",
@@ -28,15 +31,17 @@ SYSTEMS = [
"host": "10.10.30.210", "host": "10.10.30.210",
"port": 81, "port": 81,
"address": "10.10.30.210:81", "address": "10.10.30.210:81",
"layer": "Access" "layer": "Access Layer",
"label": "Proxy"
}, },
{ {
"name": "Dashy", "name": "Dashy",
"type": "Dashboard", "type": "Service Dashboard",
"host": "10.10.30.61", "host": "10.10.30.61",
"port": 4000, "port": 4000,
"address": "10.10.30.61:4000", "address": "10.10.30.61:4000",
"layer": "Monitoring" "layer": "Dashboard",
"label": "Portal"
}, },
{ {
"name": "Jellyfin", "name": "Jellyfin",
@@ -44,7 +49,8 @@ SYSTEMS = [
"host": "10.10.30.143", "host": "10.10.30.143",
"port": 30013, "port": 30013,
"address": "10.10.30.143:30013", "address": "10.10.30.143:30013",
"layer": "Services" "layer": "Application Service",
"label": "App"
}, },
{ {
"name": "K3s API", "name": "K3s API",
@@ -52,7 +58,8 @@ SYSTEMS = [
"host": "10.10.30.72", "host": "10.10.30.72",
"port": 6443, "port": 6443,
"address": "10.10.30.72:6443", "address": "10.10.30.72:6443",
"layer": "Compute" "layer": "Compute Cluster",
"label": "Cluster"
}, },
{ {
"name": "APRS-IS", "name": "APRS-IS",
@@ -60,17 +67,29 @@ SYSTEMS = [
"host": "noam.aprs2.net", "host": "noam.aprs2.net",
"port": 14580, "port": 14580,
"address": "noam.aprs2.net:14580", "address": "noam.aprs2.net:14580",
"layer": "Edge" "layer": "Edge / RF",
"label": "External"
} }
] ]
def check_tcp(host, port, timeout=2): def check_tcp(host, port, timeout=2):
start = time.perf_counter()
try: try:
with socket.create_connection((host, port), timeout=timeout): with socket.create_connection((host, port), timeout=timeout):
return True latency_ms = round((time.perf_counter() - start) * 1000)
return True, latency_ms
except OSError: except OSError:
return False return False, None
def health_label(online_count, total):
if online_count == total:
return "All Systems Online"
if online_count >= max(1, total - 2):
return "Degraded"
return "Action Required"
def main(): def main():
@@ -78,15 +97,19 @@ def main():
systems = [] systems = []
online_count = 0 online_count = 0
offline_count = 0
for system in SYSTEMS: for system in SYSTEMS:
online = check_tcp(system["host"], system["port"]) online, latency = check_tcp(system["host"], system["port"])
if online: if online:
status = "Online" status = "Online"
online_count += 1 online_count += 1
latency_value = f"{latency} ms"
else: else:
status = "Offline" status = "Offline"
offline_count += 1
latency_value = "N/A"
systems.append({ systems.append({
"name": system["name"], "name": system["name"],
@@ -94,16 +117,22 @@ def main():
"address": system["address"], "address": system["address"],
"status": status, "status": status,
"layer": system["layer"], "layer": system["layer"],
"label": system["label"],
"latency": latency_value,
"lastCheck": checked_at "lastCheck": checked_at
}) })
total_systems = len(SYSTEMS)
data = { data = {
"nocName": "Ced's NOC", "nocName": "Ced's NOC",
"status": "Operational Build", "status": health_label(online_count, total_systems),
"lastUpdated": checked_at, "lastUpdated": checked_at,
"summary": { "summary": {
"nodes": "12+", "nodes": "12+",
"services": f"{online_count}/{len(SYSTEMS)}", "services": f"{online_count}/{total_systems}",
"online": str(online_count),
"offline": str(offline_count),
"vlans": "4", "vlans": "4",
"edgeSystems": "2" "edgeSystems": "2"
}, },
@@ -116,7 +145,8 @@ def main():
json.dump(data, file, indent=2) json.dump(data, file, indent=2)
print(f"NOC status written to {OUTPUT_FILE}") print(f"NOC status written to {OUTPUT_FILE}")
print(f"Online systems: {online_count}/{len(SYSTEMS)}") print(f"Online systems: {online_count}/{total_systems}")
print(f"Offline systems: {offline_count}/{total_systems}")
if __name__ == "__main__": if __name__ == "__main__":
+30
View File
@@ -581,3 +581,33 @@ a:hover {
font-weight: 500; font-weight: 500;
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.system-label {
display: inline-block;
margin-top: 0.85rem;
margin-bottom: 0.35rem;
padding: 0.25rem 0.55rem;
border-radius: 999px;
background: rgba(88, 166, 255, 0.12);
border: 1px solid rgba(88, 166, 255, 0.22);
color: var(--accent);
font-size: 0.72rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.system-meta {
display: grid;
gap: 0.25rem;
margin-top: 0.85rem;
}
.system-meta small {
color: var(--muted);
}
.system-meta small:first-child {
color: var(--accent);
font-weight: 800;
}