mirror of
https://github.com/ced4568/ced-portfolio.git
synced 2026-08-13 06:44:02 +00:00
Upgrade NOC dashboard to V3 with latency and roadmap
This commit is contained in:
+34
-18
@@ -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
@@ -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>📊 Ced’s NOC Vision</h2>
|
<h2>📊 Ced’s 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.
|
Ced’s 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">
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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__":
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user