Files
vacuum-wall/webui/static/pages/dashboard.js
T
2026-06-17 03:41:08 +00:00

111 lines
4.9 KiB
JavaScript

import { h, PageHeader, Badge, StatusDot, Empty, Card, esc, enc, att_esc, $val, apiFetch, toast, dismissToast, openModal, closeModal, formModal, definePage, hComp, connect, reactive, requestUpdate, Link, createRouter, ToastContainer, render, parseZones } from '/static/hoover/index.js';
export default definePage({
init() {
return { data: null, loading: true, refreshing: false, error: null };
},
subscribe: ['*'],
async load(state, abortController, entry) {
const myId = entry ? entry.requestId : 0;
if (state.data) state.refreshing = true;
else state.loading = true;
try {
const res = await apiFetch('/api/status/all', { signal: abortController?.signal });
if (abortController?.signal.aborted || entry.requestId !== myId) return;
if (res.ok) state.data = res.data;
else state.error = res.error;
} catch (e) {
if (abortController?.signal.aborted) return;
state.error = String(e);
}
state.loading = false;
state.refreshing = false;
},
render(state) {
if (state.loading && !state.refreshing) {
return [
PageHeader({ title: 'Dashboard', subtitle: 'System overview' }),
h('div', { class: 'card', key: 'loading' },
h('div', { class: 'card-body loading' }, state.refreshing ? 'Refreshing...' : 'Loading...'),
),
];
}
if (state.error) {
return [
PageHeader({ title: 'Dashboard', subtitle: 'System overview' }),
h('div', { class: 'card', key: 'error' },
h('div', { class: 'card-body error-msg' }, state.error),
),
];
}
const d = state.data;
if (!d) {
return [
PageHeader({ title: 'Dashboard', subtitle: 'System overview' }),
h('div', { class: 'card', key: 'no-data' },
h('div', { class: 'card-body loading' }, 'No data available'),
),
];
}
const fwZones = (d.firewall?.zones) || {};
const net = d.net || {};
const nCount = Object.keys(net).length;
const upI = Object.values(net).filter(i => i.state === 'up');
const upC = upI.length;
const certs = d.certs || [];
const certW = certs.filter(c => c.expired || c.days_remaining <= 30);
const dmsk = d.dnsmasq?.status || {};
const dmskUp = dmsk.state === 'up';
const wUp = (d.wg?.state || 'down') === 'up';
const wP = (d.wg || {}).peers || [];
return [
PageHeader({ title: 'Dashboard', subtitle: 'System overview' }),
h('div', { class: 'grid grid-4' },
h('div', { class: 'stat-card' },
h('div', { class: 'label' }, 'Active Zones'),
h('div', { class: 'value' }, Object.keys(fwZones).length),
h('div', { class: 'meta' }, Object.keys(fwZones).join(', ') || 'None'),
),
h('div', { class: 'stat-card' },
h('div', { class: 'label' }, 'Interfaces Up'),
h('div', { class: 'value' }, upC + '/' + nCount),
h('div', { class: 'meta' }, upI.map(i => i.name).join(', ') || 'None up'),
),
h('div', { class: 'stat-card' },
h('div', { class: 'label' }, 'WireGuard'),
h('div', { class: 'value' }, String(d.wg?.state || 'unknown')),
h('div', { class: 'meta' }, wP.length + ' peers'),
),
h('div', { class: 'stat-card' },
h('div', { class: 'label' }, 'Certificates'),
h('div', { class: 'value' }, certs.length),
h('div', { class: 'meta' }, certW.length + ' expiring/expired'),
),
),
h('div', { class: 'grid grid-2' },
h('div', { class: 'card' },
h('div', { class: 'card-header' }, 'Services'),
h('div', { class: 'card-body' },
h('ul', { class: 'service-list' },
h('li', null,
StatusDot({ status: dmskUp ? 'success' : 'danger' }),
' Dnsmasq ',
Badge({ text: dmsk.state || 'down', variant: dmskUp ? 'success' : 'danger' }),
),
h('li', null,
StatusDot({ status: wUp ? 'success' : 'danger' }),
' WireGuard ',
Badge({ text: String(d.wg?.state || 'down'), variant: wUp ? 'success' : 'danger' }),
),
),
),
),
),
];
},
});