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.type}
+${system.address}
+
+ `;
+
+ 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 ` +${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.type}
-${system.address}
-
-
`;
+ }).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 → + +
+