From 64b418a55166ba06aa5fd06d10a3209f7f83364a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 4 Oct 2025 12:45:35 -0400 Subject: [PATCH] v0.4.8 - Implement web server functionality for embedded admin interface - serve HTML/CSS/JS from /api/ endpoint with proper MIME types, CORS headers, and performance optimizations --- .roo/commands/push.md | 4 +- .rooignore | 1 + Makefile | 2 +- README.md | 28 + api/index copy.html | 4095 +++++++++++++++++ api/index.css | 455 ++ api/index.html | 3660 +-------------- api/index.js | 3277 +++++++++++++ api/nostr-lite.js | 1816 ++++++-- ...51589a19b7749911f5344bb85e5fa3163f0.db-shm | Bin 0 -> 32768 bytes ...51589a19b7749911f5344bb85e5fa3163f0.db-wal | Bin 0 -> 486192 bytes deploy_local.sh | 3 + docs/user_guide.md | 36 +- embed_web_files.sh | 128 + nip_11_curl.sh | 3 + relay.pid | 2 +- src/api.c | 165 + src/api.h | 20 + src/config.c | 386 +- src/embedded_web_content.c | 56 + src/embedded_web_content.h | 19 + src/nip011.c | 186 +- src/sql_schema.h | 61 +- src/websockets.c | 161 +- src/websockets.h | 1 + test_dynamic_config.sh | 133 - test_nip50_search.sh | 97 - tests/stats_query_test.sh | 129 + 28 files changed, 10635 insertions(+), 4289 deletions(-) create mode 100644 .rooignore create mode 100644 api/index copy.html create mode 100644 api/index.css create mode 100644 api/index.js create mode 100644 b1cfc168265a1b699187f9fd417cb51589a19b7749911f5344bb85e5fa3163f0.db-shm create mode 100644 b1cfc168265a1b699187f9fd417cb51589a19b7749911f5344bb85e5fa3163f0.db-wal create mode 100755 deploy_local.sh create mode 100755 embed_web_files.sh create mode 100755 nip_11_curl.sh create mode 100644 src/api.c create mode 100644 src/api.h create mode 100644 src/embedded_web_content.c create mode 100644 src/embedded_web_content.h delete mode 100755 test_dynamic_config.sh delete mode 100644 test_nip50_search.sh create mode 100755 tests/stats_query_test.sh diff --git a/.roo/commands/push.md b/.roo/commands/push.md index f01dca2..b9f1e17 100644 --- a/.roo/commands/push.md +++ b/.roo/commands/push.md @@ -2,4 +2,6 @@ description: "Brief description of what this command does" --- -Run build_and_push.sh, and supply a good git commit message. \ No newline at end of file +Run build_and_push.sh, and supply a good git commit message. For example: + +./build_and_push.sh "Fixed the bug with nip05 implementation" \ No newline at end of file diff --git a/.rooignore b/.rooignore new file mode 100644 index 0000000..4a46470 --- /dev/null +++ b/.rooignore @@ -0,0 +1 @@ +src/embedded_web_content.c \ No newline at end of file diff --git a/Makefile b/Makefile index ab05d92..41480f2 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k BUILD_DIR = build # Source files -MAIN_SRC = src/main.c src/config.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c +MAIN_SRC = src/main.c src/config.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a # Architecture detection diff --git a/README.md b/README.md index 359c40f..b73af0a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,18 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo - [x] NIP-50: Keywords filter - [x] NIP-70: Protected Events +## 🌐 Web Admin Interface + +C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides: + +- **Real-time Configuration Management**: View and edit all relay settings through a web UI +- **Database Statistics Dashboard**: Monitor event counts, storage usage, and performance metrics +- **Auth Rules Management**: Configure whitelist/blacklist rules for pubkeys +- **NIP-42 Authentication**: Secure access using your Nostr identity +- **Event-Based Updates**: All changes are applied as cryptographically signed Nostr events + +The web interface serves embedded static files with no external dependencies and includes proper CORS headers for browser compatibility. + ## 🔧 Administrator API C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. All admin commands use **NIP-44 encrypted command arrays** for security and compatibility. @@ -87,6 +99,7 @@ All commands are sent as NIP-44 encrypted JSON arrays in the event content. The | **System Commands** | | `system_clear_auth` | `["system_command", "clear_all_auth_rules"]` | Clear all auth rules | | `system_status` | `["system_command", "system_status"]` | Get system status | +| `stats_query` | `["stats_query"]` | Get comprehensive database statistics | ### Available Configuration Keys @@ -229,3 +242,18 @@ All admin commands return **signed EVENT responses** via WebSocket following sta "sig": "response_event_signature" }] ``` + +**Database Statistics Query Response:** +```json +["EVENT", "temp_sub_id", { + "id": "response_event_id", + "pubkey": "relay_public_key", + "created_at": 1234567890, + "kind": 23457, + "content": "nip44 encrypted:{\"query_type\": \"stats_query\", \"timestamp\": 1234567890, \"database_size_bytes\": 1048576, \"total_events\": 15432, \"database_created_at\": 1234567800, \"latest_event_at\": 1234567890, \"event_kinds\": [{\"kind\": 1, \"count\": 12000, \"percentage\": 77.8}, {\"kind\": 0, \"count\": 2500, \"percentage\": 16.2}], \"time_stats\": {\"total\": 15432, \"last_24h\": 234, \"last_7d\": 1456, \"last_30d\": 5432}, \"top_pubkeys\": [{\"pubkey\": \"abc123...\", \"event_count\": 1234, \"percentage\": 8.0}, {\"pubkey\": \"def456...\", \"event_count\": 987, \"percentage\": 6.4}]}", + "tags": [ + ["p", "admin_public_key"] + ], + "sig": "response_event_signature" +}] +``` diff --git a/api/index copy.html b/api/index copy.html new file mode 100644 index 0000000..e5d251d --- /dev/null +++ b/api/index copy.html @@ -0,0 +1,4095 @@ + + + + + + + C-Relay Admin API + + + + +

C-RELAY ADMIN API

+ + +
+ + +
+ +
+ + +
+
+

NOSTR AUTHENTICATION

+

Please login with your Nostr identity to access the admin interface.

+ +
+
+ + +
+
+

RELAY CONNECTION

+ +
+ + +
+ +
+ + + +
+ +
+ + + +
+ +
NOT CONNECTED
+ + + +
+
+ + + + +
+ + + + + + + + + + + + + + + + +
+
+

DATABASE STATISTICS

+
NOT LOADED
+
+

Real-time database statistics and metrics. Login required for admin access.

+ + +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValueDescription
Database Size-Current database file size
Total Events-Total number of events stored
Oldest Event-Timestamp of oldest event
Newest Event-Timestamp of newest event
+
+
+ + +
+ +
+ + + + + + + + + + + + + +
Event KindCountPercentage
No data loaded
+
+
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
PeriodEventsDescription
Last 24 Hours-Events in the last day
Last 7 Days-Events in the last week
Last 30 Days-Events in the last month
+
+
+ + +
+ +
+ + + + + + + + + + + + + + +
RankPubkeyEvent CountPercentage
No data loaded
+
+
+
+ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/index.css b/api/index.css new file mode 100644 index 0000000..4ddb81e --- /dev/null +++ b/api/index.css @@ -0,0 +1,455 @@ +:root { + /* Core Variables (7) */ + --primary-color: #000000; + --secondary-color: #ffffff; + --accent-color: #ff0000; + --muted-color: #dddddd; + --border-color: var(--muted-color); + --font-family: "Courier New", Courier, monospace; + --border-radius: 15px; + --border-width: 1px; + + /* Floating Tab Variables (8) */ + --tab-bg-logged-out: #ffffff; + --tab-bg-logged-in: #ffffff; + --tab-bg-opacity-logged-out: 0.9; + --tab-bg-opacity-logged-in: 0.2; + --tab-color-logged-out: #000000; + --tab-color-logged-in: #ffffff; + --tab-border-logged-out: #000000; + --tab-border-logged-in: #ff0000; + --tab-border-opacity-logged-out: 1.0; + --tab-border-opacity-logged-in: 0.1; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: var(--font-family); + background-color: var(--secondary-color); + color: var(--primary-color); + /* line-height: 1.4; */ + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +h1 { + border-bottom: var(--border-width) solid var(--border-color); + padding-bottom: 10px; + margin-bottom: 30px; + font-weight: normal; + font-size: 24px; + font-family: var(--font-family); + color: var(--primary-color); +} + +h2 { + font-weight: normal; + padding-left: 10px; + font-size: 16px; + font-family: var(--font-family); + color: var(--primary-color); +} + +.section { + background: var(--secondary-color); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + padding: 20px; + margin-bottom: 20px; +} + +.input-group { + margin-bottom: 15px; +} + +label { + display: block; + margin-bottom: 5px; + font-weight: bold; + font-size: 14px; + font-family: var(--font-family); + color: var(--primary-color); +} + +input, +textarea, +select { + width: 100%; + padding: 8px; + background: var(--secondary-color); + color: var(--primary-color); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + font-family: var(--font-family); + font-size: 14px; + box-sizing: border-box; + transition: all 0.2s ease; +} + +input:focus, +textarea:focus, +select:focus { + border-color: var(--accent-color); + outline: none; +} + +button { + width: 100%; + padding: 8px; + background: var(--secondary-color); + color: var(--primary-color); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + font-family: var(--font-family); + font-size: 14px; + cursor: pointer; + margin: 5px 0; + font-weight: bold; + transition: all 0.2s ease; +} + +button:hover { + border-color: var(--accent-color); +} + +button:active { + background: var(--accent-color); + color: var(--secondary-color); +} + +button:disabled { + background-color: #ccc; + color: var(--muted-color); + cursor: not-allowed; + border-color: #ccc; +} + +.status { + padding: 10px; + margin: 10px 0; + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + font-weight: bold; + font-family: var(--font-family); + transition: all 0.2s ease; +} + +.status.connected { + background-color: var(--primary-color); + color: var(--secondary-color); +} + +.status.disconnected { + background-color: var(--secondary-color); + color: var(--primary-color); +} + +.status.authenticated { + background-color: var(--primary-color); + color: var(--secondary-color); +} + +.status.error { + background-color: var(--secondary-color); + color: var(--primary-color); + border-color: var(--accent-color); +} + +.config-table { + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin: 10px 0; + overflow: hidden; +} + +.config-table th, +.config-table td { + border: 0.1px solid var(--muted-color); + padding: 4px; + text-align: left; + font-family: var(--font-family); + font-size: 10px; +} + +.config-table-container { + overflow-x: auto; + max-width: 100%; +} + +.config-table th { + font-weight: bold; +} + +.config-table tr:hover { + background-color: var(--muted-color); +} + +.json-display { + background-color: var(--secondary-color); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + padding: 10px; + font-family: var(--font-family); + font-size: 12px; + white-space: pre-wrap; + max-height: 300px; + overflow-y: auto; + margin: 10px 0; +} + +.log-panel { + height: 200px; + overflow-y: auto; + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + padding: 10px; + font-size: 12px; + background-color: var(--secondary-color); + font-family: var(--font-family); +} + +.log-entry { + margin-bottom: 5px; + border-bottom: 1px solid var(--muted-color); + padding-bottom: 5px; +} + +.log-timestamp { + font-weight: bold; + font-family: var(--font-family); +} + +.inline-buttons { + display: flex; + gap: 10px; +} + +.inline-buttons button { + flex: 1; +} + +.user-info { + padding: 10px; + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + margin: 10px 0; + background-color: var(--secondary-color); +} + +.user-info-container { + display: flex; + align-items: flex-start; + gap: 20px; +} + +.user-details { + flex: 1; +} + +.login-logout-btn { + width: auto; + min-width: 120px; + padding: 12px 16px; + background: var(--secondary-color); + color: var(--primary-color); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + font-family: var(--font-family); + font-size: 14px; + font-weight: bold; + cursor: pointer; + transition: all 0.2s ease; + margin: 0; + flex-shrink: 0; +} + +.login-logout-btn:hover { + border-color: var(--accent-color); +} + +.login-logout-btn:active { + background: var(--accent-color); + color: var(--secondary-color); +} + +.login-logout-btn.logout-state { + background: var(--accent-color); + color: var(--secondary-color); + border-color: var(--accent-color); +} + +.login-logout-btn.logout-state:hover { + background: var(--primary-color); + border-color: var(--border-color); +} + +.user-pubkey { + font-family: var(--font-family); + font-size: 12px; + word-break: break-all; + margin: 5px 0; +} + +.hidden { + display: none; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + border-bottom: var(--border-width) solid var(--border-color); + padding-bottom: 10px; +} + +.auth-rules-controls { + margin-bottom: 15px; +} + +.section-header .status { + margin: 0; + padding: 5px 10px; + min-width: auto; + font-size: 12px; +} + +/* Auth Rule Input Sections Styling */ +.auth-rule-section { + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + padding: 15px; + margin: 15px 0; + background-color: var(--secondary-color); +} + +.auth-rule-section h3 { + margin: 0 0 10px 0; + font-size: 14px; + font-weight: bold; + border-left: 4px solid var(--border-color); + padding-left: 8px; + font-family: var(--font-family); + color: var(--primary-color); +} + +.auth-rule-section p { + margin: 0 0 15px 0; + font-size: 13px; + color: var(--muted-color); + font-family: var(--font-family); +} + +.rule-status { + margin-top: 10px; + padding: 8px; + border: var(--border-width) solid var(--muted-color); + border-radius: var(--border-radius); + font-size: 12px; + min-height: 20px; + background-color: var(--secondary-color); + font-family: var(--font-family); + transition: all 0.2s ease; +} + +.rule-status.success { + border-color: #4CAF50; + background-color: #E8F5E8; + color: #2E7D32; +} + +.rule-status.error { + border-color: var(--accent-color); + background-color: #FFEBEE; + color: #C62828; +} + +.rule-status.warning { + border-color: #FF9800; + background-color: #FFF3E0; + color: #E65100; +} + +.warning-box { + border: var(--border-width) solid #FF9800; + border-radius: var(--border-radius); + background-color: #FFF3E0; + padding: 10px; + margin: 10px 0; + font-size: 13px; + color: #E65100; + font-family: var(--font-family); +} + +.warning-box strong { + color: #D84315; +} + +#login-section { + text-align: center; + padding: 20px; +} + +/* Floating tab styles */ +.floating-tab { + font-family: var(--font-family); + border-radius: var(--border-radius); + border: var(--border-width) solid; + transition: all 0.2s ease; +} + +.floating-tab--logged-out { + background: rgba(255, 255, 255, var(--tab-bg-opacity-logged-out)); + color: var(--tab-color-logged-out); + border-color: rgba(0, 0, 0, var(--tab-border-opacity-logged-out)); +} + +.floating-tab--logged-in { + background: rgba(0, 0, 0, var(--tab-bg-opacity-logged-in)); + color: var(--tab-color-logged-in); + border-color: rgba(255, 0, 0, var(--tab-border-opacity-logged-in)); +} + +.transition { + transition: all 0.2s ease; +} + +/* Main Sections Wrapper */ +.main-sections-wrapper { + display: flex; + flex-wrap: wrap; + gap: var(--border-width); + margin-bottom: 20px; +} + +.flex-section { + flex: 1; + min-width: 300px; +} + +@media (max-width: 700px) { + body { + padding: 10px; + } + + .inline-buttons { + flex-direction: column; + } + + h1 { + font-size: 20px; + } + + h2 { + font-size: 14px; + } +} \ No newline at end of file diff --git a/api/index.html b/api/index.html index e37e414..4066434 100644 --- a/api/index.html +++ b/api/index.html @@ -5,464 +5,7 @@ C-Relay Admin API - + @@ -646,3116 +189,149 @@ - +
-

ADMIN API TESTS

-

Test the admin API functionality with real-time event logging. Login required for authenticated tests.

+
+

DATABASE STATISTICS

+
+ - +
- -
-
- SYSTEM: Test interface ready. Click buttons below to test admin - API functions. -
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValueDescription
Database Size-Current database file size
Total Events-Total number of events stored
Oldest Event-Timestamp of oldest event
Newest Event-Timestamp of newest event
-
- +
- -
- - -
-
- - -
-
- - + +
+ + + + + + + + + + + + + +
Event KindCountPercentage
No data loaded
- -