(() => { 'use strict'; // ── Mobile Nav Toggle ── const navToggle = document.getElementById('nav-toggle'); const nav = document.getElementById('nav'); navToggle.addEventListener('click', () => { const open = nav.classList.toggle('open'); navToggle.setAttribute('aria-expanded', open); navToggle.querySelector('i').className = open ? 'ri-close-line' : 'ri-menu-line'; }); // Close nav on link click (mobile) nav.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { nav.classList.remove('open'); navToggle.setAttribute('aria-expanded', 'false'); navToggle.querySelector('i').className = 'ri-menu-line'; }); }); // ── Header Scroll Effect ── const header = document.getElementById('header'); let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { header.classList.toggle('scrolled', window.scrollY > 50); ticking = false; }); ticking = true; } }); // ── Status Monitor ── class StatusMonitor { constructor() { this.services = [ { id: 'status-glances', url: '/proxy/glances/api/4/quicklook', parse: (data) => `CPU ${Math.round(data.cpu_percent)}% | RAM ${Math.round(data.mem_percent)}%` }, { id: 'status-uptime', url: '/proxy/uptime/api/status-page/heartbeat/default', parse: (data) => { const groups = data.heartbeatList || {}; const monitors = Object.values(groups); const total = monitors.length; const up = monitors.filter(beats => beats.length > 0 && beats[beats.length - 1].status === 1).length; return `${up}/${total} services up`; } }, { id: 'status-cadvisor', url: '/proxy/cadvisor/api/v1.0/machine', parse: (data) => { const cores = data.num_cores || '?'; const ramGB = data.memory_capacity ? (data.memory_capacity / 1073741824).toFixed(0) : '?'; return `${cores} cores | ${ramGB} GB RAM`; } } ]; this.poll(); setInterval(() => this.poll(), 60000); } async poll() { this.services.forEach(svc => this.check(svc)); } async check(svc) { const card = document.getElementById(svc.id); if (!card) return; const dot = card.querySelector('.status-dot'); const value = card.querySelector('.status-value'); dot.className = 'status-dot loading'; try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const res = await fetch(svc.url, { signal: controller.signal }); clearTimeout(timeout); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); dot.className = 'status-dot ok'; value.textContent = svc.parse(data); } catch { dot.className = 'status-dot error'; value.textContent = 'Unreachable'; } } } new StatusMonitor(); })();