From c168081c7ef2b268bd2ea7eede51b94e4c416414 Mon Sep 17 00:00:00 2001
From: alexpaynex <55271826-alexpaynex@users.noreply.replit.com>
Date: Thu, 19 Mar 2026 20:44:19 +0000
Subject: [PATCH] task/33: Relay admin panel at /api/admin/relay
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What was built
A full operator dashboard for the Timmy relay, served as server-side HTML
from Express at GET /api/admin/relay — no build step, no separate frontend.
Follows the existing ui.ts pattern with vanilla JS.
## New API endpoint
GET /api/admin/relay/stats (added to admin-relay.ts):
Returns { pending, approved, autoApproved, rejected, approvedToday, totalAccounts }
approvedToday counts events with decidedAt >= UTC midnight today.
Uses Drizzle groupBy on relayEventQueue.status + count(*) aggregate.
Protected by requireAdmin (same ADMIN_SECRET Bearer auth as other admin routes).
## Admin panel (admin-relay-panel.ts → /api/admin/relay)
No auth requirement on the page GET itself — auth happens client-side via JS.
Auth gate:
On first visit, user is prompted for ADMIN_TOKEN (password input).
Token verified against GET /api/admin/relay/stats (401 = wrong token).
Token stored in localStorage ('relay_admin_token'); loaded on boot.
Logout clears localStorage and stops the 15s refresh timer.
Token sent as Bearer Authorization header on every API call.
Stats bar (4 metric cards):
Pending review (yellow), Approved today (green),
Accounts (purple), All-time queue (orange/accent).
Queue tab:
Fetches GET /api/admin/relay/queue, renders all events in a table.
Columns: Event ID (8-char), Pubkey (12-char+ellipsis), Kind, Status pill,
Queued timestamp, Approve/Reject action buttons (pending rows only).
Auto-refreshes every 15 seconds alongside stats.
Approve/Reject call POST /api/admin/relay/queue/:id/approve|reject.
Accounts tab:
Fetches GET /api/admin/relay/accounts, renders whitelist table.
Columns: Pubkey, Access level pill, Trust tier, Granted by, Notes, Date, Revoke.
Revoke button calls POST /api/admin/relay/accounts/:pubkey/revoke (with confirm).
Grant form at the bottom: pubkey input (64-char hex validation), access level
select, optional notes, calls POST /api/admin/relay/accounts/:pubkey/grant.
Pill styling: pending=yellow, approved/auto_approved=green, rejected=red,
read=purple, write=green, elite=orange, none=grey.
Navigation links: ← Timmy UI, Workshop, Log out.
## Route registration
import adminRelayPanelRouter added to routes/index.ts; router.use() registered
between adminRelayQueueRouter and demoRouter.
## TypeScript: 0 errors. Smoke tests:
- GET /api/admin/relay → 200 HTML with correct
✓
- GET /api/admin/relay/stats (localhost) → 200 with all 6 fields ✓
- Auth gate renders correctly in browser ✓
---
.../src/routes/admin-relay-panel.ts | 782 ++++++++++++++++++
.../api-server/src/routes/admin-relay.ts | 44 +-
artifacts/api-server/src/routes/index.ts | 2 +
3 files changed, 825 insertions(+), 3 deletions(-)
create mode 100644 artifacts/api-server/src/routes/admin-relay-panel.ts
diff --git a/artifacts/api-server/src/routes/admin-relay-panel.ts b/artifacts/api-server/src/routes/admin-relay-panel.ts
new file mode 100644
index 0000000..3205eda
--- /dev/null
+++ b/artifacts/api-server/src/routes/admin-relay-panel.ts
@@ -0,0 +1,782 @@
+/**
+ * admin-relay-panel.ts — Serves the relay admin dashboard HTML at /admin/relay.
+ *
+ * This is a self-contained vanilla-JS SPA served as inline HTML from Express.
+ * Auth gate: on first visit the user is prompted for ADMIN_TOKEN, which is
+ * stored in localStorage and sent as Bearer on every API call.
+ *
+ * Tabs:
+ * Queue — Pending events list with Approve / Reject; auto-refreshes every 15s
+ * Accounts — Whitelist table with Revoke; pubkey grant form
+ *
+ * Stats bar at top: pending, approved today, total accounts.
+ */
+
+import { Router } from "express";
+
+const router = Router();
+
+router.get("/admin/relay", (_req, res) => {
+ res.setHeader("Content-Type", "text/html");
+ res.send(ADMIN_PANEL_HTML);
+});
+
+export default router;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// HTML is defined as a const so the file stays a valid TS module with no imports
+// at runtime and no build step required.
+// ─────────────────────────────────────────────────────────────────────────────
+
+const ADMIN_PANEL_HTML = `
+
+
+
+
+Relay Admin — Timmy Tower World
+
+
+
+
+
+
+
🔒 Admin Access
+
Enter the relay admin token to access the dashboard. It will be remembered in this browser.