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

207 lines
8.3 KiB
Markdown

# 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