Files
Void-Homelab/public/views/cards/backups.js
2026-06-09 17:49:00 +10:00

51 lines
2.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// public/views/cards/backups.js — offsite DR backup status (Core-4 -> Farm/Won).
// Fed by /usr/local/bin/offsite-backup.sh which POSTs each run to /api/backups.
import { el, mount } from '../../dom.js';
import { api } from '../../api.js';
let body, timer;
const gb = b => (b == null ? ''
: b >= 1e12 ? (b / 1e12).toFixed(1) + 'T'
: b >= 1e9 ? (b / 1e9).toFixed(1) + 'G'
: Math.round(b / 1e6) + 'M');
function ago(ts) {
const s = Math.max(0, (Date.now() - Date.parse(ts)) / 1000);
if (s < 3600) return Math.floor(s / 60) + 'm';
if (s < 86400) return Math.floor(s / 3600) + 'h';
return Math.floor(s / 86400) + 'd';
}
async function load() {
if (!body) return;
try {
const d = await api.get('/api/backups');
const r = d.latest;
if (!r) { mount(body, el('span', { class: 'muted' }, 'No offsite backups yet.')); return; }
const stale = (Date.now() - Date.parse(r.ran_at)) > 8 * 86400000; // >8d overdue
const status = (!r.ok || stale) ? 'bad' : 'ok';
const kids = [];
kids.push(el('div', { class: 'sv-row' },
el('span', { class: 'k' }, 'Last run'),
el('span', { class: 'cl-badge ' + status }, r.ok ? ago(r.ran_at) + ' ago' : 'FAILED')));
kids.push(el('div', { class: 'sv-row' },
el('span', { class: 'k' }, 'Pushed to Farm'), el('span', {}, gb(r.total_bytes))));
for (const g of (r.guests || []))
kids.push(el('div', { class: 'sv-row' },
el('span', { class: 'k' }, 'CT ' + g.vmid + ' ' + g.name),
el('span', { class: 'muted' }, gb(g.bytes))));
kids.push(el('div', { class: 'sv-row' },
el('span', { class: 'k' }, 'Farm free'), el('span', {}, gb(r.won_free_bytes))));
kids.push(el('div', { class: 'sv-row' },
el('span', { class: 'k' }, 'Schedule'), el('span', { class: 'muted' }, d.schedule || 'weekly')));
mount(body, el('div', { class: 'sv-cluster' }, ...kids));
} catch { mount(body, el('span', { class: 'muted' }, 'Backups unavailable')); }
}
export default {
id: 'backups', title: 'Backups · offsite', size: 's',
mount(e) { body = e; load(); },
start() { timer = setInterval(load, 60000); },
stop() { clearInterval(timer); body = null; }
};