Files
Homepage/assets/js/main.js
2026-02-12 22:18:40 +01:00

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();
})();