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

8.3 KiB

admin2: Full PHP Admin for C-Relay-PG

Goal

Recreate the entire c-relay-pg admin interface as a PHP application in a new admin2/ directory, starting from the existing api/ HTML/CSS/JS files and replacing the Nostr admin command backend with direct PostgreSQL queries via PHP PDO. This covers ALL admin sections, not just caching.

Architecture

Same as admin/ (PHP + nginx + PDO + 10s polling), but with the full section set from the original api/index.html:

flowchart LR
    Browser -->|HTTPS /admin2/| Nginx
    Nginx -->|*.php| PHP_FPM
    Nginx -->|/relay/| Relay[C-Relay-PG]
    PHP_FPM -->|PDO| PostgreSQL
    Relay -->|libpq| PostgreSQL

Sections (9 pages, matching original nav)

# Nav Label Section ID Data Source PHP API Endpoint
1 Statistics databaseStatisticsSection pg_database_size(), events count, pg_stat_activity, process stats api/stats.php
2 Subscriptions subscriptionDetailsSection subscriptions + subscription_metrics tables api/subscriptions.php
3 Configuration div_config config table (read/edit all keys) api/config.php
4 Authorization authRulesSection + wotSection auth_rules table api/auth.php
5 IP BAN ipBansSection ip_bans table api/ipbans.php
6 Relay Events relayEventsSection events table (recent events, kind filter) api/events.php
7 Caching cachingSection caching_* tables (already built in admin/) api/caching.php
8 DM nip17DMSection events table (kind 4/14/15 DMs) api/dm.php
9 Database Query sqlQuerySection Direct SQL execution (admin only) api/query.php

Graph on Statistics Page

The original has an event-rate chart. For the PHP version:

  • X-axis: one tick = 10 seconds (the refresh interval)
  • Y-axis: events per interval
  • Data source: api/stats.php returns events_delta (count of new events since last poll)
  • JS: maintains a rolling array of the last 60 data points (10 minutes of history), appends a new point on each 10s refresh, renders as a simple canvas/SVG line chart
  • No external chart library — use a lightweight inline canvas renderer to keep it dependency-free

File Structure

admin2/
├── index.php              ← Single-page app (all 9 sections in one page, show/hide via JS)
├── assets/
│   ├── index.css          ← Copied from api/index.css (unchanged)
│   └── app.js             ← Adapted from api/index.js (replace Nostr commands with fetch() to PHP APIs)
├── lib/
│   ├── config.php         ← DB connection config (same as admin/)
│   ├── db.php             ← PDO helper (same as admin/)
│   └── helpers.php        ← Shared helpers (same as admin/)
├── api/
│   ├── stats.php          ← Statistics: DB size, event counts, process info, event-rate delta
│   ├── subscriptions.php  ← Subscription details + metrics
│   ├── config.php         ← Config table read/edit (all keys, not just caching)
│   ├── auth.php           ← Auth rules + WoT status
│   ├── ipbans.php         ← IP ban list (read/add/remove)
│   ├── events.php         ← Recent relay events (paginated, kind filter)
│   ├── caching.php        ← Caching status (reuses admin/api/status.php logic)
│   ├── dm.php             ← DM list (kind 4/14/15 events)
│   └── query.php          ← Direct SQL query execution (admin only, read-only by default)
└── README.md

Implementation Plan

Step 1 — Scaffold admin2/ directory

  • Create admin2/ with lib/ (copy from admin/), assets/, api/
  • Copy api/index.cssadmin2/assets/index.css (unchanged)
  • Copy api/index.htmladmin2/index.php (will be adapted)

Step 2 — Adapt index.php from api/index.html

  • Remove Nostr login/auth (NIP-07 extension, kind-23456 admin commands)
  • Remove WebSocket connection logic (replaced by PHP AJAX polling)
  • Keep all 9 section HTML structures exactly as-is
  • Keep the side-nav, header, section show/hide JS
  • Replace sendAdminCommand() calls with fetch('api/*.php') calls
  • Add 10s auto-refresh via setInterval for statistics page
  • Add event-rate chart canvas renderer

Step 3 — Adapt app.js from api/index.js

  • Keep: switchPage(), nav handling, section show/hide, config table rendering
  • Remove: WebSocket connection, NIP-07 login, NIP-44 encryption, kind-23456 event sending
  • Replace: each sendAdminCommand(['system_command', '...']) with fetch('api/....php')
  • Add: refreshStats() function that polls api/stats.php every 10s
  • Add: event-rate chart rendering on canvas (rolling 60-point window)

Step 4 — Build PHP API endpoints (9 files)

Each endpoint returns JSON, queried directly from PostgreSQL via PDO:

api/stats.php — Statistics page data:

SELECT pg_database_size(current_database()) AS db_size;
SELECT COUNT(*) FROM events;
SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'active';
-- Event rate: count events with first_seen in last 10s
SELECT COUNT(*) FROM events WHERE first_seen >= EXTRACT(EPOCH FROM NOW())::BIGINT - 10;
-- Kind distribution
SELECT kind, COUNT(*) FROM events GROUP BY kind ORDER BY count DESC;

api/config.php — Configuration (all config keys, not just caching):

SELECT key, value FROM config ORDER BY key;
-- POST: UPDATE config SET value = ? WHERE key = ?

api/subscriptions.php — Subscription details:

SELECT * FROM subscriptions ORDER BY created_at DESC LIMIT 100;
SELECT * FROM subscription_metrics ORDER BY id DESC LIMIT 100;

api/auth.php — Auth rules:

SELECT * FROM auth_rules ORDER BY id;

api/ipbans.php — IP bans:

SELECT * FROM ip_bans ORDER BY banned_at DESC;
-- POST: INSERT/DELETE

api/events.php — Recent relay events (paginated):

SELECT id, pubkey, kind, created_at, content, tags
FROM events ORDER BY created_at DESC LIMIT 50 OFFSET ?;
-- Filter by kind, pubkey

api/caching.php — Caching status (reuse from admin/):

SELECT * FROM caching_service_state;
SELECT * FROM caching_followed_pubkeys LIMIT 50 OFFSET ?;
SELECT * FROM caching_backfill_relay_progress LIMIT 50 OFFSET ?;

api/dm.php — Direct messages:

SELECT id, pubkey, kind, created_at, content
FROM events WHERE kind IN (4, 14, 15) ORDER BY created_at DESC LIMIT 50;

api/query.php — SQL query (admin only):

-- Execute arbitrary SELECT, return JSON
-- Read-only by default; write mode behind a flag

Step 5 — Event-rate chart

  • Canvas element in the statistics section
  • JS maintains eventRateHistory = [] (max 60 points)
  • On each 10s refresh, push new event count, shift if > 60
  • Render as line chart on canvas (no external library)
  • X-axis labels: time (mm:ss), one tick per 10s
  • Y-axis labels: event count per interval

Step 6 — nginx config

Add /admin2/ location block (same pattern as /admin/):

location ^~ /admin2/ {
    alias /opt/c-relay-pg/admin2/;
    index index.php;
    auth_basic "Relay Admin";
    auth_basic_user_file /opt/c-relay-pg/admin/.htpasswd;
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
    }
    location ^~ /admin2/lib/ { deny all; }
}

Step 7 — Deploy and test

  • Copy admin2/ to server
  • Add nginx location block, reload
  • Test all 9 sections
  • Verify 10s auto-refresh on statistics page
  • Verify event-rate chart updates

Key Differences from Original api/

Aspect Original (api/) New (admin2/)
Auth NIP-07 Nostr extension login HTTP Basic Auth (nginx)
Data fetch WebSocket + kind-23456 encrypted admin commands PHP AJAX fetch() to api/*.php
Encryption NIP-44 (64KB limit) None (direct DB, no limit)
Real-time WebSocket push 10s polling
Backend C relay binary (embedded JS) PHP-FPM + PostgreSQL PDO
Sections 9 (same) 9 (same HTML, different data source)

Out of Scope

  • NIP-07 Nostr login (use HTTP Basic Auth)
  • WebSocket push (use 10s polling)
  • Write operations that require relay-level logic (event publishing, NIP-42 auth) — those stay on the Nostr admin API
  • The original api/ page remains unchanged and functional