Clean NOC dashboard UI with real inventory data

This commit is contained in:
2026-04-25 15:04:02 -05:00
parent bf510d74a4
commit 8926acb805
4 changed files with 276 additions and 126 deletions
+69
View File
@@ -0,0 +1,69 @@
{
"nocName": "Ced's NOC",
"status": "Operational Build",
"lastUpdated": "Manual V1",
"summary": {
"nodes": "12+",
"services": "10+",
"vlans": "4",
"edgeSystems": "2"
},
"systems": [
{
"name": "Proxmox VE",
"type": "Virtualization",
"address": "10.10.30.250:8006",
"status": "Online",
"layer": "Infrastructure"
},
{
"name": "TrueNAS",
"type": "Storage",
"address": "10.10.30.143",
"status": "Online",
"layer": "Storage"
},
{
"name": "Nginx Proxy Manager",
"type": "Reverse Proxy",
"address": "10.10.30.210:81",
"status": "Online",
"layer": "Access"
},
{
"name": "Dashy",
"type": "Dashboard",
"address": "10.10.30.61:4000",
"status": "Online",
"layer": "Monitoring"
},
{
"name": "Jellyfin",
"type": "Media Service",
"address": "10.10.30.143:30013",
"status": "Online",
"layer": "Services"
},
{
"name": "K3s Cluster",
"type": "Kubernetes",
"address": "10.10.30.72 / 245 / 128",
"status": "Active Build",
"layer": "Compute"
},
{
"name": "APRS Home iGate",
"type": "RF Edge System",
"address": "KJ5JCO-10",
"status": "Operational / Tuning",
"layer": "Edge"
},
{
"name": "APRS Mobile iGate",
"type": "Mobile Edge System",
"address": "KJ5JCO-15",
"status": "Building",
"layer": "Edge"
}
]
}
+43 -43
View File
@@ -22,6 +22,7 @@
<nav>
<ul class="nav-links">
<li><a href="#snapshot">Snapshot</a></li>
<li><a href="#noc">NOC</a></li>
<li><a href="#about">About</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#resume">Resume</a></li>
@@ -103,48 +104,6 @@ building_real_systems</code></pre>
</p>
</div>
<section class="section noc-dashboard">
<div class="container">
<div class="section-heading">
<p class="section-label">Ceds NOC</p>
<h2>Network Operations Dashboard</h2>
<p class="section-intro">
A portfolio view of the infrastructure, edge systems, and monitoring stack Im building across Ceds HomeLab.
</p>
</div>
<div class="noc-status-grid">
<div class="noc-status-card online">
<span class="status-dot"></span>
<h3>HomeLab Core</h3>
<p>Infrastructure services, VLANs, dashboards, and self-hosted systems.</p>
<small>Status: Active Build</small>
</div>
<div class="noc-status-card online">
<span class="status-dot"></span>
<h3>K3s Cluster</h3>
<p>Distributed container environment for service deployment and orchestration.</p>
<small>Status: Active Build</small>
</div>
<div class="noc-status-card warning">
<span class="status-dot"></span>
<h3>APRS iGate</h3>
<p>RF-to-IP edge system using Raspberry Pi, Direwolf, APRS-IS, and mobile expansion.</p>
<small>Status: Operational / Tuning</small>
</div>
<div class="noc-status-card planned">
<span class="status-dot"></span>
<h3>Unified Monitoring</h3>
<p>Future Grafana/Uptime Kuma view for services, nodes, and edge devices.</p>
<small>Status: Planned</small>
</div>
</div>
</div>
</section>
<div class="stats-grid">
<div class="stat-card">
<h3>12+</h3>
@@ -173,6 +132,47 @@ building_real_systems</code></pre>
</div>
</section>
<section id="noc" class="section noc-dashboard">
<div class="container">
<div class="section-heading">
<p class="section-label">Ceds NOC</p>
<h2>Live Systems Overview</h2>
<p class="section-intro">
Real infrastructure inventory from Ceds HomeLab, K3s cluster, and APRS edge systems.
</p>
</div>
<div class="noc-command-bar">
<span class="status-dot"></span>
<span id="noc-status-text">Loading NOC status...</span>
</div>
<div class="noc-metrics-grid">
<div class="noc-metric">
<h3 id="metric-nodes">--</h3>
<p>Nodes</p>
</div>
<div class="noc-metric">
<h3 id="metric-services">--</h3>
<p>Services</p>
</div>
<div class="noc-metric">
<h3 id="metric-vlans">--</h3>
<p>VLANs</p>
</div>
<div class="noc-metric">
<h3 id="metric-edge">--</h3>
<p>Edge Systems</p>
</div>
</div>
<div id="noc-system-grid" class="noc-system-grid"></div>
</div>
</section>
<section id="about" class="section">
<div class="container">
<div class="section-heading">
@@ -440,7 +440,7 @@ building_real_systems</code></pre>
<footer class="site-footer">
<div class="container footer-content">
<p>© Chase Dumphord</p>
<p>© <span data-year></span> Chase Dumphord</p>
<a href="#top" class="back-to-top">Back to top ↑</a>
</div>
</footer>
+69
View File
@@ -3,4 +3,73 @@ document.addEventListener("DOMContentLoaded", () => {
if (yearSpan) {
yearSpan.textContent = new Date().getFullYear();
}
loadNocStatus();
});
async function loadNocStatus() {
try {
const response = await fetch("data/noc-status.json");
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
const statusText = document.getElementById("noc-status-text");
const nodes = document.getElementById("metric-nodes");
const services = document.getElementById("metric-services");
const vlans = document.getElementById("metric-vlans");
const edge = document.getElementById("metric-edge");
const grid = document.getElementById("noc-system-grid");
if (!statusText || !nodes || !services || !vlans || !edge || !grid) {
return;
}
statusText.textContent = `${data.nocName}${data.status}${data.lastUpdated}`;
nodes.textContent = data.summary.nodes;
services.textContent = data.summary.services;
vlans.textContent = data.summary.vlans;
edge.textContent = data.summary.edgeSystems;
grid.innerHTML = "";
data.systems.forEach((system) => {
const card = document.createElement("article");
card.className = "noc-system-card";
const status = system.status.toLowerCase();
const statusClass =
status.includes("online") || status.includes("operational")
? "online"
: status.includes("building") || status.includes("tuning") || status.includes("active")
? "warning"
: "planned";
card.innerHTML = `
<div class="noc-system-top">
<span class="mini-status ${statusClass}"></span>
<span>${system.layer}</span>
</div>
<h3>${system.name}</h3>
<p>${system.type}</p>
<code>${system.address}</code>
<small>Status: ${system.status}</small>
`;
grid.appendChild(card);
});
} catch (error) {
const statusText = document.getElementById("noc-status-text");
if (statusText) {
statusText.textContent = "Ceds NOC data unavailable";
}
console.error("Failed to load NOC status:", error);
}
}
+94 -82
View File
@@ -187,6 +187,8 @@ a:hover {
.project-card,
.stat-card,
.noc-card,
.noc-metric,
.noc-system-card,
.personal-image-wrap {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.025));
@@ -195,10 +197,6 @@ a:hover {
box-shadow: var(--shadow);
}
.hero-image-card {
padding: 1rem;
}
.hero-image {
border-radius: 16px;
width: 100%;
@@ -270,7 +268,9 @@ a:hover {
.contact-grid,
.personal-grid,
.noc-grid,
.stats-grid {
.stats-grid,
.noc-metrics-grid,
.noc-system-grid {
display: grid;
gap: 1.25rem;
}
@@ -289,18 +289,21 @@ a:hover {
}
.stats-grid,
.noc-grid {
.noc-metrics-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.noc-grid {
.noc-grid,
.noc-system-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.card,
.project-card,
.stat-card,
.noc-card {
.noc-card,
.noc-metric,
.noc-system-card {
padding: 1.35rem;
}
@@ -364,13 +367,15 @@ a:hover {
font-weight: 800;
}
.stat-card h3 {
.stat-card h3,
.noc-metric h3 {
margin: 0;
font-size: 2.2rem;
color: var(--accent-2);
}
.stat-card p {
.stat-card p,
.noc-metric p {
margin: 0.25rem 0;
font-weight: 800;
color: var(--text);
@@ -381,6 +386,79 @@ a:hover {
font-size: 0.92rem;
}
.noc-dashboard {
background:
radial-gradient(circle at top left, rgba(142, 240, 207, 0.08), transparent 30%),
linear-gradient(180deg, #070b12 0%, #0d1422 100%);
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
}
.noc-command-bar {
display: flex;
align-items: center;
gap: 0.75rem;
background: rgba(0, 0, 0, 0.28);
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.9rem 1rem;
margin-bottom: 1.25rem;
color: var(--muted);
}
.status-dot,
.mini-status {
width: 11px;
height: 11px;
border-radius: 50%;
display: inline-block;
background: var(--accent-2);
box-shadow: 0 0 16px rgba(142, 240, 207, 0.9);
}
.mini-status.warning {
background: var(--warning);
box-shadow: 0 0 16px rgba(242, 204, 96, 0.9);
}
.mini-status.planned {
background: var(--accent);
box-shadow: 0 0 16px rgba(88, 166, 255, 0.9);
}
.noc-system-top {
display: flex;
align-items: center;
justify-content: space-between;
color: var(--muted);
font-size: 0.85rem;
}
.noc-system-card h3 {
margin: 0.8rem 0 0.35rem;
}
.noc-system-card p {
color: var(--muted);
margin: 0.2rem 0 0.6rem;
}
.noc-system-card code {
display: inline-block;
color: var(--accent-2);
background: rgba(0, 0, 0, 0.25);
padding: 0.25rem 0.45rem;
border-radius: 8px;
font-size: 0.82rem;
}
.noc-system-card small {
display: block;
margin-top: 0.8rem;
color: var(--accent);
font-weight: 700;
}
.noc-section {
background:
linear-gradient(135deg, rgba(88, 166, 255, 0.08), rgba(142, 240, 207, 0.04));
@@ -423,7 +501,9 @@ a:hover {
@media (max-width: 1050px) {
.projects-grid,
.stats-grid {
.stats-grid,
.noc-metrics-grid,
.noc-system-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@@ -456,7 +536,9 @@ a:hover {
@media (max-width: 620px) {
.projects-grid,
.stats-grid {
.stats-grid,
.noc-metrics-grid,
.noc-system-grid {
grid-template-columns: 1fr;
}
@@ -478,73 +560,3 @@ a:hover {
gap: 0.5rem;
}
}
.noc-dashboard {
background:
radial-gradient(circle at top left, rgba(142, 240, 207, 0.08), transparent 30%),
linear-gradient(180deg, #070b12 0%, #0d1422 100%);
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
}
.noc-status-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1.25rem;
}
.noc-status-card {
position: relative;
padding: 1.35rem;
border-radius: var(--radius);
background: rgba(17, 28, 46, 0.92);
border: 1px solid var(--line);
box-shadow: var(--shadow);
}
.noc-status-card h3 {
margin-top: 0.7rem;
margin-bottom: 0.5rem;
}
.noc-status-card p {
color: var(--muted);
}
.noc-status-card small {
display: inline-block;
margin-top: 0.8rem;
color: var(--accent-2);
font-weight: 700;
}
.status-dot {
width: 11px;
height: 11px;
border-radius: 50%;
display: inline-block;
background: var(--accent-2);
box-shadow: 0 0 16px rgba(142, 240, 207, 0.9);
}
.noc-status-card.warning .status-dot {
background: var(--warning);
box-shadow: 0 0 16px rgba(242, 204, 96, 0.9);
}
.noc-status-card.planned .status-dot {
background: var(--accent);
box-shadow: 0 0 16px rgba(88, 166, 255, 0.9);
}
@media (max-width: 1050px) {
.noc-status-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 620px) {
.noc-status-grid {
grid-template-columns: 1fr;
}
}