mirror of
https://github.com/ced4568/ceds-noc.git
synced 2026-08-12 21:04:02 +00:00
Clean NOC dashboard UI with real inventory data
This commit is contained in:
@@ -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
@@ -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">Ced’s NOC</p>
|
||||
<h2>Network Operations Dashboard</h2>
|
||||
<p class="section-intro">
|
||||
A portfolio view of the infrastructure, edge systems, and monitoring stack I’m building across Ced’s 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">Ced’s NOC</p>
|
||||
<h2>Live Systems Overview</h2>
|
||||
<p class="section-intro">
|
||||
Real infrastructure inventory from Ced’s 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>
|
||||
|
||||
@@ -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 = "Ced’s NOC data unavailable";
|
||||
}
|
||||
|
||||
console.error("Failed to load NOC status:", error);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user