diff --git a/index.html b/index.html
index 25afaa8..74dc008 100644
--- a/index.html
+++ b/index.html
@@ -91,7 +91,7 @@
Built using real infrastructure: Proxmox • K3s • TrueNAS • VLAN segmentation • APRS RF Edge Systems
-
+
+
+
+
diff --git a/script.js b/script.js
index 4adc1ca..1b3de68 100644
--- a/script.js
+++ b/script.js
@@ -5,8 +5,6 @@ document.addEventListener("DOMContentLoaded", () => {
}
loadNocStatus();
-
- // Auto-refresh NOC dashboard every 30 seconds
setInterval(loadNocStatus, 30000);
});
@@ -26,6 +24,7 @@ async function loadNocStatus() {
const vlans = document.getElementById("metric-vlans");
const edge = document.getElementById("metric-edge");
const grid = document.getElementById("noc-system-grid");
+ const alertList = document.getElementById("noc-alert-list");
if (!statusText || !nodes || !services || !vlans || !edge || !grid) {
return;
@@ -33,10 +32,10 @@ async function loadNocStatus() {
statusText.textContent = `${data.nocName} — ${data.status} — Last updated: ${data.lastUpdated}`;
- nodes.textContent = data.summary.nodes;
- services.textContent = data.summary.services;
- vlans.textContent = data.summary.vlans;
- edge.textContent = data.summary.edgeSystems;
+ animateMetric(nodes, data.summary.nodes);
+ animateMetric(services, data.summary.services);
+ animateMetric(vlans, data.summary.vlans);
+ animateMetric(edge, data.summary.edgeSystems);
grid.innerHTML = "";
@@ -78,6 +77,10 @@ async function loadNocStatus() {
grid.appendChild(card);
});
+
+ if (alertList) {
+ renderAlerts(data.systems, alertList);
+ }
} catch (error) {
const statusText = document.getElementById("noc-status-text");
@@ -87,4 +90,59 @@ async function loadNocStatus() {
console.error("Failed to load NOC status:", error);
}
+}
+
+function animateMetric(element, value) {
+ element.classList.remove("metric-pop");
+ element.textContent = value;
+
+ void element.offsetWidth;
+ element.classList.add("metric-pop");
+}
+
+function renderAlerts(systems, alertList) {
+ const priority = systems
+ .map((system) => {
+ const severity = (system.severity || "Unknown").toLowerCase();
+ const status = (system.status || "").toLowerCase();
+
+ let score = 0;
+
+ if (severity.includes("critical") || status.includes("offline")) {
+ score = 3;
+ } else if (severity.includes("degraded")) {
+ score = 2;
+ } else if (severity.includes("unknown")) {
+ score = 1;
+ }
+
+ return { ...system, score };
+ })
+ .filter((system) => system.score > 0)
+ .sort((a, b) => b.score - a.score)
+ .slice(0, 3);
+
+ if (priority.length === 0) {
+ alertList.innerHTML = `
+
+ All monitored systems healthy
+ No critical or degraded systems detected.
+
+ `;
+ return;
+ }
+
+ alertList.innerHTML = priority
+ .map((system) => {
+ const alertClass =
+ system.score === 3 ? "critical" : system.score === 2 ? "degraded" : "unknown";
+
+ return `
+
+ ${system.name}
+ ${system.severity || system.status} • ${system.layer} • ${system.latency || "N/A"}
+
+ `;
+ })
+ .join("");
}
\ No newline at end of file
diff --git a/style.css b/style.css
index f347cb3..a8afa7f 100644
--- a/style.css
+++ b/style.css
@@ -647,4 +647,81 @@ a:hover {
color: #ff5f56;
background: rgba(255, 95, 86, 0.1);
border: 1px solid rgba(255, 95, 86, 0.28);
+}
+
+.noc-alert-panel {
+ margin: 1.5rem 0;
+ padding: 1.25rem;
+ border-radius: var(--radius);
+ background: rgba(0, 0, 0, 0.22);
+ border: 1px solid var(--line);
+ box-shadow: var(--shadow);
+}
+
+.noc-alert-header {
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.noc-alert-header h3 {
+ margin: 0;
+}
+
+.noc-alert-header span {
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.noc-alert-list {
+ display: grid;
+ gap: 0.75rem;
+}
+
+.noc-alert {
+ display: grid;
+ gap: 0.2rem;
+ padding: 0.9rem 1rem;
+ border-radius: 14px;
+ border-left: 4px solid var(--accent-2);
+ background: rgba(255, 255, 255, 0.035);
+}
+
+.noc-alert span {
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.noc-alert.healthy {
+ border-left-color: var(--accent-2);
+}
+
+.noc-alert.degraded {
+ border-left-color: var(--warning);
+}
+
+.noc-alert.critical {
+ border-left-color: #ff5f56;
+}
+
+.noc-alert.unknown {
+ border-left-color: var(--accent);
+}
+
+.metric-pop {
+ animation: metricPop 0.35s ease;
+}
+
+@keyframes metricPop {
+ 0% {
+ transform: scale(0.96);
+ opacity: 0.7;
+ }
+
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
}
\ No newline at end of file