/* ═══════════════════════════════════════════════════════════════
ADMIN PANEL
Superadmin only, guarded by RequireRole on the route and by
requireRole("superadmin") on every endpoint it calls. This page
assumes neither: it renders whatever the API gives it and shows
whatever the API refuses.
Three blocks, deliberately boring:
System — what's actually running, for when something is off
Content — row counts, the cheapest "is the database there"
Accounts — roles, access and live sessions
The role select is driven by ROLES from lib/roles.ts, so a new
rung on the ladder appears here without this file changing. The
legend beside it is the same list — four roles is past the
point where "Editor" explains itself.
Every account write signs that person out, which the server
does rather than this page. Two things you can't do here: edit
your own row, or take the last active superadmin away. Both are
enforced server-side and mirrored in the disabled states, so
the reason shows up before the click rather than after it.
═══════════════════════════════════════════════════════════════ */
import { useCallback, useEffect, useState } from "react";
import { del, get, patch } from "../../lib/api.ts";
import { isUnauthorized, useAuth } from "../../lib/auth.tsx";
import { useNavigate } from "react-router-dom";
import { ROLES, ROLE_LABELS, ROLE_NOTES } from "../../lib/roles.ts";
/* SQLite hands back "2026-09-22 04:11:07" — UTC, but without the
marker that says so. Left alone, browsers read it as local time
and last-login drifts by the timezone offset. */
function when(value) {
if (!value) return "—";
const iso = value.includes("T") ? value : `${value.replace(" ", "T")}Z`;
const date = new Date(iso);
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
}
function uptime(seconds) {
if (seconds == null) return "—";
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
if (d) return `${d}d ${h}h`;
if (h) return `${h}h ${m}m`;
return `${m}m`;
}
function Block({ title, note, children }: any) {
return (
{note}{title}
{note &&
Loading the panel…
); } if (error) { return ({error}
Accounts and server state. Everything here is superadmin-only.
{system.dbPath}
)}| Account | Role | Last login | Sessions | Access |
|---|---|---|---|---|
|
{row.name || row.email}
{isMe && (
you
)}
{row.name && (
{row.email}
)}
{rowError?.id === row.id && (
{rowError.message}
)}
|
{when(row.last_login_at)} | {row.sessions} {row.sessions > 0 && ( )} |
New accounts are still created with admin-cli.js on the server.