From cc1a4bac164f478d8160d494135a612d4e28ae6f Mon Sep 17 00:00:00 2001 From: Chase Dumphord Date: Sat, 2 May 2026 17:40:10 -0500 Subject: [PATCH] Update noc-status.json fetch to absolute URL --- script.js | 341 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 232 insertions(+), 109 deletions(-) diff --git a/script.js b/script.js index 1b3de68..e1eb69a 100644 --- a/script.js +++ b/script.js @@ -1,148 +1,271 @@ -document.addEventListener("DOMContentLoaded", () => { - const yearSpan = document.querySelector("[data-year]"); - if (yearSpan) { - yearSpan.textContent = new Date().getFullYear(); - } +// ═══════════════════════════════════════════════ +// Ced's Portfolio — script.js +// NOC data loads from data/noc-status.json +// 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(); setInterval(loadNocStatus, 30000); + + // GitHub pinned repos + loadGithubRepos(); }); -async function loadNocStatus() { - try { - const response = await fetch("data/noc-status.json"); +// Track consecutive failures so we can show the fallback +let failCount = 0; +const MAX_FAILS = 3; - if (!response.ok) { - throw new Error(`HTTP error: ${response.status}`); +async function loadNocStatus() { + 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"); - 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"); + // ── Metrics ── + if (nodes) animateMetric(nodes, data.summary.nodes); + if (services) animateMetric(services, data.summary.services); + if (vlans) animateMetric(vlans, data.summary.vlans); + if (edge) animateMetric(edge, data.summary.edgeSystems); - 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 = ` +
+ + ${system.layer} +
+
${system.label || "System"}
+
+ ${system.severity || "Unknown"} +
+

${system.name}

+

${system.type}

+ ${system.address} +
+ Status: ${system.status} + Latency: ${system.latency || "N/A"} + Last Check: ${system.lastCheck || "Manual"} +
+ `; + + 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 = `

No public repositories found.

`; 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); - animateMetric(services, data.summary.services); - animateMetric(vlans, data.summary.vlans); - animateMetric(edge, data.summary.edgeSystems); + const updatedDate = new Date(repo.pushed_at).toLocaleDateString("en-US", { + month: "short", + year: "numeric", + }); - grid.innerHTML = ""; + return ` +
+
+ 📁 + ${repo.name} + ${repo.private ? "private" : "public"} +
- data.systems.forEach((system) => { - const card = document.createElement("article"); - card.className = "noc-system-card"; +

${desc}

- const status = system.status.toLowerCase(); - - const statusClass = - status.includes("online") || status.includes("operational") - ? "online" - : status.includes("building") || status.includes("tuning") || status.includes("active") - ? "warning" - : "offline"; - - card.innerHTML = ` -
- - ${system.layer} -
- -
${system.label || "System"}
- -
- ${system.severity || "Unknown"} -
- -

${system.name}

-

${system.type}

- ${system.address} - -
- Status: ${system.status} - Latency: ${system.latency || "N/A"} - Last Check: ${system.lastCheck || "Manual"} +
+ ${repo.language + ? ` + + ${repo.language} + ` + : ""} + ★ ${repo.stargazers_count} + Updated ${updatedDate} +
`; + }).join(""); - grid.appendChild(card); - }); - - if (alertList) { - renderAlerts(data.systems, alertList); + } catch (err) { + console.warn("[GitHub] Failed to load repos:", err.message); + if (container) { + container.innerHTML = ` +
+

+ Could not load repositories. + + View on GitHub → + +

+
+ `; } - } 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); } } - -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(); + const scored = systems + .map((sys) => { + const sev = (sys.severity || "").toLowerCase(); + const status = (sys.status || "").toLowerCase(); 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")) { - score = 3; - } else if (severity.includes("degraded")) { - score = 2; - } else if (severity.includes("unknown")) { - score = 1; - } - - return { ...system, score }; + return { ...sys, score }; }) - .filter((system) => system.score > 0) + .filter((s) => s.score > 0) .sort((a, b) => b.score - a.score) .slice(0, 3); - if (priority.length === 0) { + if (scored.length === 0) { alertList.innerHTML = `
- All monitored systems healthy + All monitored systems nominal No critical or degraded systems detected.
`; return; } - alertList.innerHTML = priority - .map((system) => { - const alertClass = - system.score === 3 ? "critical" : system.score === 2 ? "degraded" : "unknown"; + alertList.innerHTML = scored.map((sys) => { + const cls = + sys.score === 3 ? "critical" + : sys.score === 2 ? "degraded" + : "unknown"; - return ` -
- ${system.name} - ${system.severity || system.status} • ${system.layer} • ${system.latency || "N/A"} -
- `; - }) - .join(""); -} \ No newline at end of file + return ` +
+ ${sys.name} + ${sys.severity || sys.status} • ${sys.layer} • Latency: ${sys.latency || "N/A"} +
+ `; + }).join(""); +}