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.phpreturnsevents_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/withlib/(copy fromadmin/),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 withfetch('api/*.php')calls - Add 10s auto-refresh via
setIntervalfor 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', '...'])withfetch('api/....php') - Add:
refreshStats()function that pollsapi/stats.phpevery 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