Files
study-bible-project/server/health.js
nmemmertandClaude Sonnet 5.5 bdb04cdedc
Build and Push Docker Image / build-and-push (push) Successful in 10m22s
Add cross-reference map, admin health page, passkeys and device list, settings tabs
- Reader: chapter cross-reference map (M) from IQ Bible's reference graph
- Admin: Health tab with disk use, snapshots (back up, download, restore), failure logs
- Passkey sign-in (WebAuthn), signed-in device list, sign out others, optional sign-in alerts
- Settings split into Account, Preferences, Notifications, Security, Your data tabs
- ntfy server field: catch a topic typed into the server box

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 12:52:49 -04:00

38 lines
1.7 KiB
JavaScript

// What the admin's Health page shows: a short in-memory log of things that went wrong (failed sign-ins, failed
// notifications, backups), plus disk use and the snapshot list. The log resets when the server restarts.
import { existsSync, statSync, statfsSync } from 'fs';
import { join } from 'path';
import { dataDir } from './db.js';
import { backupDir, listSnapshots } from './backups.js';
const MAX_EVENTS = 200;
const events = [];
const started = Date.now();
// kind: 'signin' | 'notify' | 'backup'. `ok` false marks a failure.
export function logEvent(kind, detail, { ok = false, userId = null } = {}) {
events.unshift({ at: Date.now(), kind, ok, detail: String(detail).slice(0, 300), userId });
if (events.length > MAX_EVENTS) events.length = MAX_EVENTS;
}
export const recentEvents = (kind, limit = 50) => events.filter((e) => !kind || e.kind === kind).slice(0, limit);
export function clearEvents() { events.length = 0; }
const size = (path) => { try { return statSync(path).size; } catch { return 0; } };
export function diskUse() {
const dir = dataDir();
const db = size(join(dir, 'projects.db')) + size(join(dir, 'projects.db-wal'));
const backups = listSnapshots(backupDir()).reduce((n, s) => n + s.bytes, 0);
let free = null; let total = null;
try { const s = statfsSync(existsSync(dir) ? dir : '.'); free = s.bavail * s.bsize; total = s.blocks * s.bsize; } catch { /* not supported here */ }
return { db, backups, free, total };
}
export function backupStatus() {
const snapshots = listSnapshots(backupDir());
return { enabled: process.env.BACKUP_DISABLED !== '1', keep: Number(process.env.BACKUP_KEEP) || 7, snapshots, latest: snapshots[0] ?? null };
}
export const uptimeSeconds = () => Math.round((Date.now() - started) / 1000);