Files
c-relay-pg/plans/relay_admin_migration_plan.md
T

12 KiB

Relay Admin Page Migration Plan

Goal

Migrate the relay admin functionality from the internally-served api/index.html + api/index.js (served by the C relay binary) to an externally-hosted relay-admin.html page in the client-ndk project. This page uses the NDK SharedWorker architecture (template.html pattern) and communicates with the relay via kind 23456/23457/24567 Nostr events.

Current State

Working Reference: c-relay-pg.html

  • Successfully receives kind 24567 monitoring events
  • Has SUBSCRIPTION_ONLY_MODE = true — admin UI actions are disabled
  • Contains all admin section HTML (Statistics, Subscriptions, Configuration, Authorization, IP Bans, Relay Events, DM, Database Query)
  • Has sidenav with admin page links
  • Has sendAdminCommand(), encryptForRelay(), decryptFromRelay(), fetchRelayInfo() functions
  • Has handleMonitoringEvent() and handleAdminResponse() event handlers

Target File: relay-admin.html

  • Currently an exact copy of template.html
  • Has full template infrastructure: hamburger menu, sidenav, footer, auth system, relay-ui, blossom-ui, ai-ui
  • Has the newer injectHeaderLoginButton import (slightly newer than c-relay-pg.html)
  • Empty divBody — ready for admin content

Legacy Internal Pages: api/index.html + api/index.js

  • 6,779 lines of JavaScript — the full admin UI
  • Uses nostr-tools SimplePool directly (not NDK)
  • Uses NOSTR_LOGIN_LITE for auth (not NDK SharedWorker)
  • Has rich UI: config tables with inline editing, IP ban management with filters, SQL query console with history, WoT management, relay event forms
  • All admin commands go through sendAdminCommand() → kind 23456 with NIP-44 encryption
  • Responses come back as kind 23457 events, decrypted and routed to handlers

Architecture Comparison

graph TD
    subgraph Legacy - api/index.html
        A1[NOSTR_LOGIN_LITE auth] --> A2[SimplePool direct WS]
        A2 --> A3[Kind 23456 admin commands]
        A2 --> A4[Kind 24567 monitoring sub]
        A3 --> A5[Kind 23457 responses]
    end

    subgraph New - relay-admin.html
        B1[NDK SharedWorker auth] --> B2[NDK subscribe/publishEvent]
        B2 --> B3[Kind 23456 admin commands]
        B2 --> B4[Kind 24567 monitoring sub]
        B3 --> B5[Kind 23457 responses]
    end

Key Difference: NDK vs SimplePool

Aspect Legacy api/index.js New relay-admin.html
Auth NOSTR_LOGIN_LITE direct initNDKPage() via SharedWorker
Subscriptions SimplePool.subscribeMany() subscribe() from init-ndk.mjs
Publishing SimplePool.publish() + manual signing publishEvent() — auto-signed by worker
Encryption window.nostr.nip44.encrypt/decrypt Same — window.nostr.nip44.encrypt/decrypt
Event listening SimplePool callbacks window.addEventListener('ndkEvent', ...)
Relay URL Auto-derived from page URL Configurable constant ADMIN_RELAY_WS_URL

Migration Strategy

The approach is to merge the admin functionality from c-relay-pg.html into relay-admin.html, removing the SUBSCRIPTION_ONLY_MODE restriction and enabling full admin UI. We use the NDK publishEvent() and subscribe() APIs instead of SimplePool.

What stays from template (relay-admin.html already has)

  • Full hamburger menu + sidenav infrastructure
  • Footer with relay status animations
  • Auth system with required/optional/none modes
  • Relay-ui, blossom-ui, ai-ui sidenav sections
  • Theme toggle, logout, version display

What gets added from c-relay-pg.html

  • Admin nav items in sidenav
  • Admin section HTML panels in divBody
  • Admin-specific CSS styles
  • Relay pubkey fetch via NIP-11
  • NIP-44 encrypt/decrypt helpers
  • sendAdminCommand() for kind 23456
  • Kind 24567 monitoring subscription
  • Kind 23457 response handling with per-section routing
  • All admin UI button handlers
  • Boot diagnostics panel
  • System info panel

Detailed Implementation Steps

The admin links go directly inside divSideNavBody as simple styled items — not inside a collapsible .sidenavSection container. They should be always-visible, non-collapsible links that match the font/styling of the sidenav sections but remain in the open body area. Place them before divFiles:

<div id="divSideNavBody">
  <!-- Admin nav links - always visible, not collapsible -->
  <div id="divAdminNav">
    <div class="adminNavItem active" data-section="statistics">Statistics</div>
    <div class="adminNavItem" data-section="subscriptions">Subscriptions</div>
    <div class="adminNavItem" data-section="configuration">Configuration</div>
    <div class="adminNavItem" data-section="authorization">Authorization</div>
    <div class="adminNavItem" data-section="ip-bans">IP Bans</div>
    <div class="adminNavItem" data-section="relay-events">Relay Events</div>
    <div class="adminNavItem" data-section="dm">DM</div>
    <div class="adminNavItem" data-section="database">Database Query</div>
  </div>
  <div id="divFiles"></div>
</div>

The .adminNavItem styling uses the same font-family and sizing as sidenav section content (font-size: 70%, font-family: var(--font-family)) with a simple border-bottom separator, hover accent color, and bold+background for the active state. No section title header, no collapse toggle icon.

Step 2: Add Admin Section HTML Panels

Port the section HTML from c-relay-pg.html into divBody. Each section follows the pattern:

<div id="section-{name}" class="adminSection">
  <div class="adminSectionHeader">{Title}</div>
  <!-- Section-specific content -->
</div>

Sections to port:

  1. Statistics — CPU metrics, time stats, top pubkeys, event kinds cards
  2. Subscriptions — Subscription details log
  3. Configuration — Config key/value inputs, fetch/set buttons, log
  4. Authorization — Pubkey input, whitelist/blacklist/query buttons, log
  5. IP Bans — IP input, duration, ban/query buttons, log
  6. Relay Events — Kind 0 metadata form, live event feed
  7. DM — Message textarea, send button, log
  8. Database Query — SQL textarea, run button, log
  9. System (always visible) — Relay WS/HTTP URLs, relay/user pubkeys
  10. Boot Diagnostics (always visible) — Boot log

Step 3: Add Admin CSS

Port the admin-specific styles from c-relay-pg.html <style> block. These use CSS variables from client.css so they integrate with the theme system:

  • #divBody flex layout overrides
  • .adminNavItem styles with hover/active states
  • .adminSection show/hide with .active class
  • .adminGrid, .adminCard, .adminTable layout
  • .adminInput, .adminTextArea, .adminBtn form elements
  • .adminLog scrollable log panels

Step 4: Add Admin State Variables

const ADMIN_RELAY_WS_URL = 'wss://relay.laantungir.net';
const ADMIN_RELAY_HTTP_URL = 'https://relay.laantungir.net';

let relayPubkey = '';
let adminInitialized = false;
let adminUiBound = false;
const adminSubscriptions = [];
const MAX_LOG_LINES = 200;

Step 5: Add Core Admin Functions

Port from c-relay-pg.html:

  1. appendLog(id, msg) — Prepend timestamped messages to log panels
  2. setSystemInfo() — Populate system info table
  3. fetchRelayInfo() — NIP-11 fetch to get relay pubkey
  4. encryptForRelay(content) — NIP-44 encrypt using window.nostr.nip44.encrypt
  5. decryptFromRelay(content) — NIP-44 decrypt using window.nostr.nip44.decrypt
  6. sendAdminCommand(commandArray, sink) — Encrypt + publish kind 23456 event via publishEvent()

Step 6: Add Subscription Setup

async function setupAdminSubscriptions() {
  // Subscribe to kind 24567 monitoring events
  const monitoringSub = subscribe(
    { kinds: [24567], since: Math.floor(Date.now() / 1000) - 60 },
    { closeOnEose: false, cacheUsage: 'ONLY_RELAY' }
  );
  
  // Subscribe to kind 23457 admin responses
  const responseSub = subscribe(
    { kinds: [23457], '#p': [currentPubkey], since: Math.floor(Date.now() / 1000) - 60 },
    { closeOnEose: false, cacheUsage: 'ONLY_RELAY' }
  );
  
  // Listen for events via ndkEvent window event
  window.addEventListener('ndkEvent', async (evt) => {
    const event = evt.detail;
    if (event.kind === 24567) handleMonitoringEvent(event);
    else if (event.kind === 23457) await handleAdminResponse(event);
    else handleLiveEvent(event);
  });
}

Step 7: Add Event Handlers

Port from c-relay-pg.html:

  1. handleMonitoringEvent(event) — Route d-tag values to stat cards
  2. handleAdminResponse(event) — Decrypt kind 23457 and route to logs
  3. handleLiveEvent(event) — Sample non-admin events to live feed
  4. activateSection(sectionKey) — Toggle active section visibility
  5. bindAdminUi() — Wire up all button click handlers

Step 8: Add Button Handlers

All handlers follow the same pattern: gather input → sendAdminCommand(array, logId):

  • btnRefreshStats['system_command', 'system_status']
  • btnConfigRefresh['config_query', 'all']
  • btnConfigSet[key, value]
  • btnWhitelist['whitelist', 'pubkey', pk]
  • btnBlacklist['blacklist', 'pubkey', pk]
  • btnAuthQuery['auth_query', 'all']
  • btnBanIp['ip_ban', 'add', ip, duration]
  • btnIpQuery['ip_ban', 'query', 'all']
  • btnPublishKind0 → Direct publishEvent() with kind 0
  • btnSendDm['dm', message]
  • btnRunSql['sql_query', query]

Step 9: Integration into main() Flow

(async function main() {
  // 1. Template init (hamburger, theme, auth)
  initHamburgerMenu();
  // ... existing template setup ...
  
  // 2. Auth
  await initializeAuthentication(authMode);
  await initializeAuthenticatedPageFeatures();
  
  // 3. Admin init (NEW)
  await initializeAdminFeatures();
  
  // 4. Template finalization
  await updateVersionDisplay();
})();

Where initializeAdminFeatures():

async function initializeAdminFeatures() {
  if (!isAuthenticated || adminInitialized) return;
  bindAdminUi();
  await fetchRelayInfo();
  await setupAdminSubscriptions();
  setSystemInfo();
  adminInitialized = true;
}

Response Routing Enhancement

The current c-relay-pg.html broadcasts all responses to all logs. For a better UX, we should parse the decrypted response and route to the appropriate log:

async function handleAdminResponse(event) {
  const plain = await decryptFromRelay(event.content);
  const parsed = JSON.parse(plain);
  
  // Route based on query_type
  if (parsed.query_type === 'config_all' || parsed.query_type === 'config_update') {
    appendLog('configLog', plain);
  } else if (parsed.query_type === 'auth_query' || parsed.query_type === 'auth_rule') {
    appendLog('authLog', plain);
  } else if (parsed.query_type === 'sql_query') {
    appendLog('sqlLog', plain);
  } else if (parsed.query_type === 'ip_ban') {
    appendLog('ipBanLog', plain);
  } else {
    // Fallback: broadcast to all
    appendLog('configLog', `RESP <- ${plain}`);
  }
}

Files Modified

File Location Change
relay-admin.html ~/lt/client-ndk/www/ Add admin HTML, CSS, and JavaScript

Files NOT Modified

  • template.html — Stays as the clean template
  • c-relay-pg.html — Stays as the debug/test reference
  • api/index.html / api/index.js — Legacy, stays for now (still served by relay binary)
  • No C code changes needed — the relay already handles kind 23456/23457/24567

Future Enhancements (Out of Scope)

  • Rich config table with inline editing (like api/index.html has)
  • IP ban table with filter tabs
  • SQL query history dropdown
  • WoT management section
  • Event rate chart visualization
  • Admin verification handshake