106 lines
3.1 KiB
JavaScript
106 lines
3.1 KiB
JavaScript
(() => {
|
|
'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();
|
|
})();
|