# 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`: ```mermaid 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.css` → `admin2/assets/index.css` (unchanged) - Copy `api/index.html` → `admin2/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: ```sql 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): ```sql SELECT key, value FROM config ORDER BY key; -- POST: UPDATE config SET value = ? WHERE key = ? ``` **api/subscriptions.php** — Subscription details: ```sql 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: ```sql SELECT * FROM auth_rules ORDER BY id; ``` **api/ipbans.php** — IP bans: ```sql SELECT * FROM ip_bans ORDER BY banned_at DESC; -- POST: INSERT/DELETE ``` **api/events.php** — Recent relay events (paginated): ```sql 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/): ```sql 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: ```sql 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): ```sql -- 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/`): ```nginx 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