mirror of
https://github.com/ced4568/ceds-noc.git
synced 2026-08-13 05:14:03 +00:00
Update noc-status.json fetch to absolute URL
This commit is contained in:
@@ -1,148 +1,271 @@
|
|||||||
document.addEventListener("DOMContentLoaded", () => {
|
// ═══════════════════════════════════════════════
|
||||||
const yearSpan = document.querySelector("[data-year]");
|
// Ced's Portfolio — script.js
|
||||||
if (yearSpan) {
|
// NOC data loads from data/noc-status.json
|
||||||
yearSpan.textContent = new Date().getFullYear();
|
// Refreshes every 30s. Graceful fallback on fail.
|
||||||
}
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// Set copyright year
|
||||||
|
const yearSpan = document.querySelector("[data-year]");
|
||||||
|
if (yearSpan) yearSpan.textContent = new Date().getFullYear();
|
||||||
|
|
||||||
|
// NOC data
|
||||||
loadNocStatus();
|
loadNocStatus();
|
||||||
setInterval(loadNocStatus, 30000);
|
setInterval(loadNocStatus, 30000);
|
||||||
|
|
||||||
|
// GitHub pinned repos
|
||||||
|
loadGithubRepos();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadNocStatus() {
|
// Track consecutive failures so we can show the fallback
|
||||||
try {
|
let failCount = 0;
|
||||||
const response = await fetch("data/noc-status.json");
|
const MAX_FAILS = 3;
|
||||||
|
|
||||||
if (!response.ok) {
|
async function loadNocStatus() {
|
||||||
throw new Error(`HTTP error: ${response.status}`);
|
const dot = document.getElementById("noc-dot");
|
||||||
|
const statusText = document.getElementById("noc-status-text");
|
||||||
|
const badge = document.getElementById("noc-refresh-badge");
|
||||||
|
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");
|
||||||
|
const alertList = document.getElementById("noc-alert-list");
|
||||||
|
const fallback = document.getElementById("noc-fallback");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cache-bust to always get fresh data
|
||||||
|
const res = await fetch(`https://chasedumphord.com/data/noc-status.json?t=${Date.now()}`);
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// Reset fail counter on success
|
||||||
|
failCount = 0;
|
||||||
|
|
||||||
|
// ── Status bar ──
|
||||||
|
if (dot) {
|
||||||
|
dot.className = "status-dot live";
|
||||||
|
}
|
||||||
|
if (statusText) {
|
||||||
|
statusText.textContent = `${data.nocName} — ${data.status} — Last updated: ${data.lastUpdated}`;
|
||||||
|
}
|
||||||
|
if (badge) {
|
||||||
|
badge.textContent = `Auto-refresh: 30s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
// Hide fallback if it was showing
|
||||||
|
if (fallback) fallback.style.display = "none";
|
||||||
|
|
||||||
const statusText = document.getElementById("noc-status-text");
|
// ── Metrics ──
|
||||||
const nodes = document.getElementById("metric-nodes");
|
if (nodes) animateMetric(nodes, data.summary.nodes);
|
||||||
const services = document.getElementById("metric-services");
|
if (services) animateMetric(services, data.summary.services);
|
||||||
const vlans = document.getElementById("metric-vlans");
|
if (vlans) animateMetric(vlans, data.summary.vlans);
|
||||||
const edge = document.getElementById("metric-edge");
|
if (edge) animateMetric(edge, data.summary.edgeSystems);
|
||||||
const grid = document.getElementById("noc-system-grid");
|
|
||||||
const alertList = document.getElementById("noc-alert-list");
|
|
||||||
|
|
||||||
if (!statusText || !nodes || !services || !vlans || !edge || !grid) {
|
// ── System cards ──
|
||||||
|
if (grid) {
|
||||||
|
grid.innerHTML = "";
|
||||||
|
data.systems.forEach((system) => {
|
||||||
|
const card = document.createElement("article");
|
||||||
|
card.className = "noc-system-card";
|
||||||
|
|
||||||
|
const rawStatus = system.status.toLowerCase();
|
||||||
|
const statusClass =
|
||||||
|
rawStatus.includes("online") || rawStatus.includes("operational") ? "online"
|
||||||
|
: rawStatus.includes("building") || rawStatus.includes("tuning") || rawStatus.includes("active") ? "warning"
|
||||||
|
: "offline";
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="noc-system-top">
|
||||||
|
<span class="mini-status ${statusClass}"></span>
|
||||||
|
<span>${system.layer}</span>
|
||||||
|
</div>
|
||||||
|
<div class="system-label">${system.label || "System"}</div>
|
||||||
|
<div class="severity-pill ${(system.severity || "unknown").toLowerCase()}">
|
||||||
|
${system.severity || "Unknown"}
|
||||||
|
</div>
|
||||||
|
<h3>${system.name}</h3>
|
||||||
|
<p>${system.type}</p>
|
||||||
|
<code>${system.address}</code>
|
||||||
|
<div class="system-meta">
|
||||||
|
<small>Status: ${system.status}</small>
|
||||||
|
<small>Latency: ${system.latency || "N/A"}</small>
|
||||||
|
<small>Last Check: ${system.lastCheck || "Manual"}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
grid.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Alerts ──
|
||||||
|
if (alertList) renderAlerts(data.systems, alertList);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
failCount++;
|
||||||
|
console.warn(`[NOC] Fetch failed (${failCount}/${MAX_FAILS}):`, err.message);
|
||||||
|
|
||||||
|
// Update dot to error state
|
||||||
|
if (dot) dot.className = "status-dot error";
|
||||||
|
|
||||||
|
if (statusText) {
|
||||||
|
statusText.textContent = failCount >= MAX_FAILS
|
||||||
|
? "NOC feed offline — view live dashboard at noc.chasedumphord.com"
|
||||||
|
: `NOC data unavailable — retrying... (${failCount}/${MAX_FAILS})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (badge) badge.textContent = "";
|
||||||
|
|
||||||
|
// After MAX_FAILS attempts, show the fallback block
|
||||||
|
if (failCount >= MAX_FAILS && fallback) {
|
||||||
|
fallback.style.display = "block";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate a number metric with a quick pop
|
||||||
|
function animateMetric(el, value) {
|
||||||
|
el.classList.remove("metric-pop");
|
||||||
|
el.textContent = value;
|
||||||
|
void el.offsetWidth; // force reflow
|
||||||
|
el.classList.add("metric-pop");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
// GitHub Repos — fetches public repos for ced4568
|
||||||
|
// Sorted by last pushed, capped at 6 cards
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Language → color map (subset of GitHub's palette)
|
||||||
|
const LANG_COLORS = {
|
||||||
|
Python: "#3572A5",
|
||||||
|
JavaScript: "#f1e05a",
|
||||||
|
Shell: "#89e051",
|
||||||
|
HTML: "#e34c26",
|
||||||
|
CSS: "#563d7c",
|
||||||
|
Dockerfile: "#384d54",
|
||||||
|
YAML: "#cb171e",
|
||||||
|
Makefile: "#427819",
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadGithubRepos() {
|
||||||
|
const container = document.getElementById("github-repos");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
"https://api.github.com/users/ced4568/repos?sort=pushed&per_page=6",
|
||||||
|
{ headers: { Accept: "application/vnd.github+json" } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error(`GitHub API ${res.status}`);
|
||||||
|
|
||||||
|
const repos = await res.json();
|
||||||
|
|
||||||
|
// Filter out forks for cleaner display
|
||||||
|
const ownRepos = repos.filter((r) => !r.fork).slice(0, 6);
|
||||||
|
|
||||||
|
if (ownRepos.length === 0) {
|
||||||
|
container.innerHTML = `<p class="noc-loading">No public repositories found.</p>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
statusText.textContent = `${data.nocName} — ${data.status} — Last updated: ${data.lastUpdated}`;
|
container.innerHTML = ownRepos.map((repo) => {
|
||||||
|
const langColor = LANG_COLORS[repo.language] || "var(--accent-2)";
|
||||||
|
const desc = repo.description
|
||||||
|
? repo.description
|
||||||
|
: "No description provided.";
|
||||||
|
|
||||||
animateMetric(nodes, data.summary.nodes);
|
const updatedDate = new Date(repo.pushed_at).toLocaleDateString("en-US", {
|
||||||
animateMetric(services, data.summary.services);
|
month: "short",
|
||||||
animateMetric(vlans, data.summary.vlans);
|
year: "numeric",
|
||||||
animateMetric(edge, data.summary.edgeSystems);
|
});
|
||||||
|
|
||||||
grid.innerHTML = "";
|
return `
|
||||||
|
<div class="repo-card">
|
||||||
|
<div class="repo-card-top">
|
||||||
|
<span class="repo-icon">📁</span>
|
||||||
|
<a
|
||||||
|
class="repo-name"
|
||||||
|
href="${repo.html_url}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
title="${repo.name}"
|
||||||
|
>${repo.name}</a>
|
||||||
|
<span class="repo-visibility">${repo.private ? "private" : "public"}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
data.systems.forEach((system) => {
|
<p class="repo-desc">${desc}</p>
|
||||||
const card = document.createElement("article");
|
|
||||||
card.className = "noc-system-card";
|
|
||||||
|
|
||||||
const status = system.status.toLowerCase();
|
<div class="repo-meta">
|
||||||
|
${repo.language
|
||||||
const statusClass =
|
? `<span>
|
||||||
status.includes("online") || status.includes("operational")
|
<span class="repo-lang-dot" style="background:${langColor}"></span>
|
||||||
? "online"
|
${repo.language}
|
||||||
: status.includes("building") || status.includes("tuning") || status.includes("active")
|
</span>`
|
||||||
? "warning"
|
: ""}
|
||||||
: "offline";
|
<span>★ ${repo.stargazers_count}</span>
|
||||||
|
<span>Updated ${updatedDate}</span>
|
||||||
card.innerHTML = `
|
</div>
|
||||||
<div class="noc-system-top">
|
|
||||||
<span class="mini-status ${statusClass}"></span>
|
|
||||||
<span>${system.layer}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="system-label">${system.label || "System"}</div>
|
|
||||||
|
|
||||||
<div class="severity-pill ${system.severity?.toLowerCase() || "unknown"}">
|
|
||||||
${system.severity || "Unknown"}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>${system.name}</h3>
|
|
||||||
<p>${system.type}</p>
|
|
||||||
<code>${system.address}</code>
|
|
||||||
|
|
||||||
<div class="system-meta">
|
|
||||||
<small>Status: ${system.status}</small>
|
|
||||||
<small>Latency: ${system.latency || "N/A"}</small>
|
|
||||||
<small>Last Check: ${system.lastCheck || "Manual"}</small>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
}).join("");
|
||||||
|
|
||||||
grid.appendChild(card);
|
} catch (err) {
|
||||||
});
|
console.warn("[GitHub] Failed to load repos:", err.message);
|
||||||
|
if (container) {
|
||||||
if (alertList) {
|
container.innerHTML = `
|
||||||
renderAlerts(data.systems, alertList);
|
<div class="repo-loading">
|
||||||
|
<p class="noc-loading">
|
||||||
|
Could not load repositories.
|
||||||
|
<a href="https://github.com/ced4568" target="_blank" rel="noopener noreferrer">
|
||||||
|
View on GitHub →
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const scored = systems
|
||||||
function animateMetric(element, value) {
|
.map((sys) => {
|
||||||
element.classList.remove("metric-pop");
|
const sev = (sys.severity || "").toLowerCase();
|
||||||
element.textContent = value;
|
const status = (sys.status || "").toLowerCase();
|
||||||
|
|
||||||
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;
|
let score = 0;
|
||||||
|
if (sev.includes("critical") || status.includes("offline")) score = 3;
|
||||||
|
else if (sev.includes("degraded")) score = 2;
|
||||||
|
else if (sev.includes("unknown")) score = 1;
|
||||||
|
|
||||||
if (severity.includes("critical") || status.includes("offline")) {
|
return { ...sys, score };
|
||||||
score = 3;
|
|
||||||
} else if (severity.includes("degraded")) {
|
|
||||||
score = 2;
|
|
||||||
} else if (severity.includes("unknown")) {
|
|
||||||
score = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...system, score };
|
|
||||||
})
|
})
|
||||||
.filter((system) => system.score > 0)
|
.filter((s) => s.score > 0)
|
||||||
.sort((a, b) => b.score - a.score)
|
.sort((a, b) => b.score - a.score)
|
||||||
.slice(0, 3);
|
.slice(0, 3);
|
||||||
|
|
||||||
if (priority.length === 0) {
|
if (scored.length === 0) {
|
||||||
alertList.innerHTML = `
|
alertList.innerHTML = `
|
||||||
<div class="noc-alert healthy">
|
<div class="noc-alert healthy">
|
||||||
<strong>All monitored systems healthy</strong>
|
<strong>All monitored systems nominal</strong>
|
||||||
<span>No critical or degraded systems detected.</span>
|
<span>No critical or degraded systems detected.</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
alertList.innerHTML = priority
|
alertList.innerHTML = scored.map((sys) => {
|
||||||
.map((system) => {
|
const cls =
|
||||||
const alertClass =
|
sys.score === 3 ? "critical"
|
||||||
system.score === 3 ? "critical" : system.score === 2 ? "degraded" : "unknown";
|
: sys.score === 2 ? "degraded"
|
||||||
|
: "unknown";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="noc-alert ${alertClass}">
|
<div class="noc-alert ${cls}">
|
||||||
<strong>${system.name}</strong>
|
<strong>${sys.name}</strong>
|
||||||
<span>${system.severity || system.status} • ${system.layer} • ${system.latency || "N/A"}</span>
|
<span>${sys.severity || sys.status} • ${sys.layer} • Latency: ${sys.latency || "N/A"}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
}).join("");
|
||||||
.join("");
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user