51 lines
2.1 KiB
JavaScript
51 lines
2.1 KiB
JavaScript
// 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; }
|
||
};
|