Add cross-reference map, admin health page, passkeys and device list, settings tabs
Build and Push Docker Image / build-and-push (push) Successful in 10m22s
Build and Push Docker Image / build-and-push (push) Successful in 10m22s
- 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>
This commit is contained in:
1 parent
207bfcb919
commit
bdb04cdedc
35 files changed
+1860
-22
No files matched your search
@@ -0,0 +1,37 @@
|
||||
// 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);
|
||||
Reference in new issue
Block a user