# 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`](/home/user/lt/client-ndk/www/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`](/home/user/lt/client-ndk/www/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.html) + [`api/index.js`](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

```mermaid
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

### Step 1: Add Admin Nav Links to Sidenav

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`:

```html
<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:

```html
<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

```javascript
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

```javascript
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

```javascript
(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()`:
```javascript
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:

```javascript
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
