Compare commits

...

1 Commits

11 changed files with 112 additions and 260 deletions

View File

@@ -54,9 +54,8 @@
<div class="section flex-section" id="databaseStatisticsSection" style="display: none;">
<div class="section-header">
<h2>DATABASE STATISTICS</h2>
<!-- Monitoring toggle button will be inserted here by JavaScript -->
<!-- Temporarily disable auto-refresh button for real-time monitoring -->
<!-- <button type="button" id="refresh-stats-btn" class="countdown-btn"></button> -->
<!-- Monitoring is now subscription-based - no toggle button needed -->
<!-- Subscribe to kind 24567 events to receive real-time monitoring data -->
</div>
<!-- Event Rate Graph Container -->

View File

@@ -850,7 +850,7 @@ async function subscribeToConfiguration() {
console.log(`Generated subscription ID: ${subscriptionId}`);
console.log(`User pubkey ${userPubkey}`)
// Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), kind 1059 (NIP-17 GiftWrap), and kind 34567 (monitoring events)
// Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), kind 1059 (NIP-17 GiftWrap), and kind 24567 (ephemeral monitoring events)
const subscription = relayPool.subscribeMany([url], [{
since: Math.floor(Date.now() / 1000) - 5, // Look back 5 seconds to avoid race condition
kinds: [23457],
@@ -870,7 +870,7 @@ async function subscribeToConfiguration() {
limit: 50
}, {
since: Math.floor(Date.now() / 1000), // Start from current time
kinds: [34567], // Real-time monitoring events
kinds: [24567], // Real-time ephemeral monitoring events
authors: [getRelayPubkey()], // Only listen to monitoring events from the relay
"#d": isLoggedIn ? ["event_kinds", "time_stats", "top_pubkeys", "active_subscriptions", "subscription_details"] : ["event_kinds", "time_stats", "top_pubkeys", "active_subscriptions"], // Include subscription_details only when authenticated
limit: 50
@@ -956,8 +956,8 @@ async function subscribeToConfiguration() {
processAdminResponse(event);
}
// Handle monitoring events (kind 34567)
if (event.kind === 34567) {
// Handle monitoring events (kind 24567 - ephemeral)
if (event.kind === 24567) {
console.log('=== MONITORING EVENT RECEIVED ===');
console.log('Monitoring event:', event);
@@ -1145,14 +1145,14 @@ function createChartStubElements() {
console.log('Chart stub elements created');
}
// Handle monitoring events (kind 34567)
// Handle monitoring events (kind 24567 - ephemeral)
async function processMonitoringEvent(event) {
try {
console.log('=== PROCESSING MONITORING EVENT ===');
console.log('Monitoring event:', event);
// Verify this is a kind 34567 monitoring event
if (event.kind !== 34567) {
// Verify this is a kind 24567 ephemeral monitoring event
if (event.kind !== 24567) {
console.log('Ignoring non-monitoring event, kind:', event.kind);
return;
}
@@ -2433,29 +2433,11 @@ async function saveAuthRule(event) {
}
}
// Auto-enable monitoring when admin logs in
// Monitoring is now subscription-based - no auto-enable needed
// Monitoring automatically activates when someone subscribes to kind 24567 events
async function autoEnableMonitoring() {
if (!isLoggedIn || !relayPool) {
log('Cannot auto-enable monitoring: not logged in or no relay connection', 'WARNING');
return;
}
try {
log('Auto-enabling monitoring for admin session...', 'INFO');
// Send enable_monitoring command
const commandArray = ["enable_monitoring"];
const requestEvent = await sendAdminCommand(commandArray);
if (requestEvent) {
log('Monitoring auto-enabled for admin session', 'INFO');
} else {
log('Failed to auto-enable monitoring', 'ERROR');
}
} catch (error) {
log(`Failed to auto-enable monitoring: ${error.message}`, 'ERROR');
}
log('Monitoring system is subscription-based - no manual enable needed', 'INFO');
log('Subscribe to kind 24567 events to receive real-time monitoring data', 'INFO');
}
// Update existing logout and showMainInterface functions to handle auth rules and NIP-17 DMs
@@ -4900,106 +4882,33 @@ function getConfigToggleButton(configKey) {
return configToggleButtons.get(configKey);
}
// Initialize toggle button for monitoring config
// Monitoring is now subscription-based - no toggle button needed
// Monitoring automatically activates when someone subscribes to kind 24567 events
function initializeMonitoringToggleButton() {
console.log('=== INITIALIZING MONITORING TOGGLE BUTTON ===');
// Check if button already exists to prevent duplicates
const existingButton = getConfigToggleButton('kind_34567_reporting_enabled');
if (existingButton) {
console.log('Monitoring toggle button already exists, skipping creation');
return existingButton;
}
// Find the DATABASE STATISTICS section header
const sectionHeader = document.querySelector('#databaseStatisticsSection .section-header h2');
console.log('Section header found:', sectionHeader);
if (!sectionHeader) {
log('Could not find DATABASE STATISTICS section header for toggle button', 'WARNING');
return;
}
// Create the toggle button
const button = new ConfigToggleButton('kind_34567_reporting_enabled', sectionHeader.parentElement, {
dataType: 'boolean',
category: 'monitoring'
});
console.log('Monitoring toggle button created:', button);
console.log('Button element:', button.button);
console.log('Button in DOM:', document.contains(button.button));
log('Monitoring toggle button initialized', 'INFO');
return button;
console.log('=== MONITORING IS NOW SUBSCRIPTION-BASED ===');
console.log('No toggle button needed - monitoring activates automatically when subscribing to kind 24567');
log('Monitoring system is subscription-based - no manual toggle required', 'INFO');
return null;
}
// Enhanced config update response handler to update toggle buttons
// Monitoring is subscription-based - no toggle button response handling needed
const originalHandleConfigUpdateResponse = handleConfigUpdateResponse;
handleConfigUpdateResponse = function(responseData) {
console.log('=== ENHANCED CONFIG UPDATE RESPONSE HANDLER ===');
console.log('=== CONFIG UPDATE RESPONSE HANDLER ===');
console.log('Response data:', responseData);
// Call original handler
originalHandleConfigUpdateResponse(responseData);
// Update toggle buttons if this was a config update response
if (responseData.query_type === 'config_update' && responseData.status === 'success' && responseData.processed_configs) {
console.log('Processing config update response for toggle buttons');
responseData.processed_configs.forEach(config => {
console.log('Processing config:', config);
const button = getConfigToggleButton(config.key);
console.log('Button found:', button);
if (button) {
const success = config.status === 'updated';
const value = String(config.value).toLowerCase();
console.log('Calling handleResponse with:', success, value);
button.handleResponse(success, value);
}
});
} else {
console.log('Not a config update response or no processed_configs');
}
// Also handle config query responses to initialize toggle buttons
if ((responseData.query_type === 'config_query' || responseData.query_type === 'config_all') && responseData.status === 'success' && responseData.data) {
console.log('Config query response - initializing toggle buttons');
initializeToggleButtonsFromConfig(responseData);
}
// Monitoring is now subscription-based - no toggle buttons to update
console.log('Monitoring system is subscription-based - no toggle buttons to handle');
};
// Initialize toggle button when config is loaded
// Monitoring is now subscription-based - no toggle buttons needed
function initializeToggleButtonsFromConfig(configData) {
console.log('=== INITIALIZING TOGGLE BUTTONS FROM CONFIG ===');
console.log('Config data:', configData);
if (!configData || !configData.data) {
console.log('No config data available');
return;
}
// Find monitoring enabled config
const monitoringConfig = configData.data.find(c => c.key === 'kind_34567_reporting_enabled');
console.log('Monitoring config found:', monitoringConfig);
if (monitoringConfig) {
const button = getConfigToggleButton('kind_34567_reporting_enabled');
console.log('Button instance:', button);
if (button) {
// Convert config value to string for state setting
const configValue = String(monitoringConfig.value).toLowerCase();
console.log('Setting button state to:', configValue);
// Set initial state from config
button.setState(configValue);
log(`Monitoring toggle button set to: ${configValue}`, 'INFO');
} else {
console.log('Button instance not found in registry - button should have been created on DOM ready');
}
} else {
console.log('Monitoring config not found in config data');
}
console.log('=== MONITORING IS SUBSCRIPTION-BASED ===');
console.log('No toggle buttons needed - monitoring activates automatically when subscribing to kind 24567');
log('Monitoring system initialized - subscription-based activation ready', 'INFO');
}
// Initialize toggle button after DOM is ready

View File

@@ -1 +1 @@
646463
774065

133
src/api.c
View File

@@ -40,28 +40,14 @@ const char* get_config_value(const char* key);
int get_config_bool(const char* key, int default_value);
int update_config_in_table(const char* key, const char* value);
// Monitoring system state
static time_t last_report_time = 0;
// Monitoring system state (throttling now handled per-function)
// Forward declaration for monitoring helper function
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void));
// Monitoring system helper functions
int is_monitoring_enabled(void) {
return get_config_bool("kind_34567_reporting_enabled", 0);
}
int get_monitoring_throttle_seconds(void) {
return get_config_int("kind_34567_reporting_throttling_sec", 5);
}
int set_monitoring_enabled(int enabled) {
const char* value = enabled ? "1" : "0";
if (update_config_in_table("kind_34567_reporting_enabled", value) == 0) {
DEBUG_INFO("Monitoring enabled state changed");
return 0;
}
return -1;
return get_config_int("kind_24567_reporting_throttle_sec", 5);
}
// Query event kind distribution from database
@@ -459,12 +445,12 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
}
free(relay_privkey_hex);
// Create monitoring event (kind 34567)
// Create monitoring event (kind 24567 - ephemeral)
cJSON* monitoring_event = cJSON_CreateObject();
cJSON_AddStringToObject(monitoring_event, "id", ""); // Will be set by signing
cJSON_AddStringToObject(monitoring_event, "pubkey", relay_pubkey);
cJSON_AddNumberToObject(monitoring_event, "created_at", (double)time(NULL));
cJSON_AddNumberToObject(monitoring_event, "kind", 34567);
cJSON_AddNumberToObject(monitoring_event, "kind", 24567);
cJSON_AddStringToObject(monitoring_event, "content", content_json);
// Create tags array with d tag for identification
@@ -480,7 +466,7 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
// Use the library function to create and sign the event
cJSON* signed_event = nostr_create_and_sign_event(
34567, // kind
24567, // kind (ephemeral)
cJSON_GetStringValue(cJSON_GetObjectItem(monitoring_event, "content")), // content
tags, // tags
relay_privkey, // private key
@@ -498,55 +484,36 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
cJSON_Delete(monitoring_event);
monitoring_event = signed_event;
// Broadcast the event to active subscriptions
// Broadcast the ephemeral event to active subscriptions (no database storage)
broadcast_event_to_subscriptions(monitoring_event);
// Store in database
int store_result = store_event(monitoring_event);
cJSON_Delete(monitoring_event);
free(content_json);
if (store_result != 0) {
DEBUG_ERROR("Failed to store monitoring event (%s)", d_tag_value);
return -1;
}
DEBUG_LOG("Monitoring event broadcast (ephemeral kind 24567, type: %s)", d_tag_value);
return 0;
}
// Monitoring hook called when an event is stored
void monitoring_on_event_stored(void) {
// Check if monitoring is enabled
if (!is_monitoring_enabled()) {
// Check throttling first (cheapest check)
static time_t last_monitoring_time = 0;
time_t current_time = time(NULL);
int throttle_seconds = get_monitoring_throttle_seconds();
if (current_time - last_monitoring_time < throttle_seconds) {
return;
}
// Check throttling
time_t now = time(NULL);
int throttle_seconds = get_monitoring_throttle_seconds();
if (now - last_report_time < throttle_seconds) {
return; // Too soon, skip this report
// Check if anyone is subscribed to monitoring events (kind 24567)
// This is the ONLY activation check needed - if someone subscribes, they want monitoring
if (!has_subscriptions_for_kind(24567)) {
return; // No subscribers = no expensive operations
}
// Generate and broadcast monitoring event
if (generate_monitoring_event() == 0) {
last_report_time = now;
}
}
// Initialize monitoring system
int init_monitoring_system(void) {
last_report_time = 0;
DEBUG_INFO("Monitoring system initialized");
return 0;
}
// Cleanup monitoring system
void cleanup_monitoring_system(void) {
// No cleanup needed for monitoring system
DEBUG_INFO("Monitoring system cleaned up");
// Generate monitoring events only when someone is listening
last_monitoring_time = current_time;
generate_monitoring_event();
}
// Forward declaration for known_configs (defined in config.c)
@@ -2265,24 +2232,8 @@ int handle_monitoring_command(cJSON* event, const char* command, char* error_mes
if (*p >= 'A' && *p <= 'Z') *p = *p + 32;
}
// Handle commands
if (strcmp(cmd, "enable_monitoring") == 0) {
if (set_monitoring_enabled(1) == 0) {
char* response_content = "✅ Monitoring enabled\n\nReal-time monitoring events will now be generated.";
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
} else {
char* response_content = "❌ Failed to enable monitoring";
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
} else if (strcmp(cmd, "disable_monitoring") == 0) {
if (set_monitoring_enabled(0) == 0) {
char* response_content = "✅ Monitoring disabled\n\nReal-time monitoring events will no longer be generated.";
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
} else {
char* response_content = "❌ Failed to disable monitoring";
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
} else if (strcmp(cmd, "set_monitoring_throttle") == 0) {
// Handle set_monitoring_throttle command (only remaining monitoring command)
if (strcmp(cmd, "set_monitoring_throttle") == 0) {
if (arg[0] == '\0') {
char* response_content = "❌ Missing throttle value\n\nUsage: set_monitoring_throttle <seconds>";
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
@@ -2298,44 +2249,28 @@ int handle_monitoring_command(cJSON* event, const char* command, char* error_mes
char throttle_str[16];
snprintf(throttle_str, sizeof(throttle_str), "%ld", throttle_seconds);
if (update_config_in_table("kind_34567_reporting_throttling_sec", throttle_str) == 0) {
if (update_config_in_table("kind_24567_reporting_throttle_sec", throttle_str) == 0) {
char response_content[256];
snprintf(response_content, sizeof(response_content),
"✅ Monitoring throttle updated\n\nMinimum interval between monitoring events: %ld seconds", throttle_seconds);
"✅ Monitoring throttle updated\n\n"
"Minimum interval between monitoring events: %ld seconds\n\n"
" Monitoring activates automatically when you subscribe to kind 24567 events.",
throttle_seconds);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
} else {
char* response_content = "❌ Failed to update monitoring throttle";
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
} else if (strcmp(cmd, "monitoring_status") == 0) {
int enabled = is_monitoring_enabled();
int throttle = get_monitoring_throttle_seconds();
} else {
char response_content[512];
snprintf(response_content, sizeof(response_content),
"📊 Monitoring Status\n"
"━━━━━━━━━━━━━━━━━━━━\n"
"\n"
"Enabled: %s\n"
"Throttle: %d seconds\n"
"\n"
"Commands:\n"
"• enable_monitoring\n"
"• disable_monitoring\n"
"• set_monitoring_throttle <seconds>\n"
"• monitoring_status",
enabled ? "Yes" : "No", throttle);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
} else {
char response_content[256];
snprintf(response_content, sizeof(response_content),
"❌ Unknown monitoring command: %s\n\n"
"Available commands:\n"
"enable_monitoring\n"
"• disable_monitoring\n"
"• set_monitoring_throttle <seconds>\n"
"• monitoring_status", cmd);
"Available command:\n"
"set_monitoring_throttle <seconds>\n\n"
" Monitoring is now subscription-based:\n"
"Subscribe to kind 24567 events to receive real-time monitoring data.\n"
"Monitoring automatically activates when subscriptions exist and deactivates when they close.",
cmd);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
}

View File

@@ -60,11 +60,7 @@ char* execute_sql_query(const char* query, const char* request_id, char* error_m
int handle_sql_query_unified(cJSON* event, const char* query, char* error_message, size_t error_size, struct lws* wsi);
// Monitoring system functions
int init_monitoring_system(void);
void cleanup_monitoring_system(void);
void monitoring_on_event_stored(void);
int set_monitoring_enabled(int enabled);
int is_monitoring_enabled(void);
int get_monitoring_throttle_seconds(void);
#endif // API_H

View File

@@ -4112,32 +4112,18 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela
return -1;
}
// Insert monitoring system config entries
// Insert monitoring system config entry (ephemeral kind 24567)
// Note: Monitoring is automatically activated when clients subscribe to kind 24567
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, "kind_34567_reporting_enabled", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, "false", -1, SQLITE_STATIC); // boolean, default false
sqlite3_bind_text(stmt, 3, "boolean", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, "Enable real-time monitoring event generation", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 5, "monitoring", -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 6, 0); // does not require restart
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
DEBUG_ERROR("Failed to insert kind_34567_reporting_enabled: %s", sqlite3_errmsg(g_db));
sqlite3_finalize(stmt);
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
return -1;
}
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, "kind_34567_reporting_throttling_sec", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 1, "kind_24567_reporting_throttle_sec", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, "5", -1, SQLITE_STATIC); // integer, default 5 seconds
sqlite3_bind_text(stmt, 3, "integer", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, "Minimum seconds between monitoring event reports", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, "Minimum seconds between monitoring event reports (ephemeral kind 24567)", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 5, "monitoring", -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 6, 0); // does not require restart
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
DEBUG_ERROR("Failed to insert kind_34567_reporting_throttling_sec: %s", sqlite3_errmsg(g_db));
DEBUG_ERROR("Failed to insert kind_24567_reporting_throttle_sec: %s", sqlite3_errmsg(g_db));
sqlite3_finalize(stmt);
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
return -1;

File diff suppressed because one or more lines are too long

View File

@@ -149,9 +149,7 @@ int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, c
// Forward declaration for database functions
int store_event(cJSON* event);
// Forward declarations for monitoring system
void init_monitoring_system(void);
void cleanup_monitoring_system(void);
// Forward declaration for monitoring system
void monitoring_on_event_stored(void);
// Forward declarations for NIP-11 relay information handling
@@ -1989,9 +1987,6 @@ int main(int argc, char* argv[]) {
// Initialize NIP-40 expiration configuration
init_expiration_config();
// Initialize monitoring system
init_monitoring_system();
// Update subscription manager configuration
update_subscription_manager_config();
@@ -2023,9 +2018,6 @@ int main(int argc, char* argv[]) {
ginxsom_request_validator_cleanup();
cleanup_configuration_system();
// Cleanup monitoring system
cleanup_monitoring_system();
// Cleanup subscription manager mutexes
pthread_mutex_destroy(&g_subscription_manager.subscriptions_lock);
pthread_mutex_destroy(&g_subscription_manager.ip_tracking_lock);

View File

@@ -10,10 +10,10 @@
#define MAIN_H
// Version information (auto-updated by build system)
#define VERSION "v0.7.32"
#define VERSION "v0.7.33"
#define VERSION_MAJOR 0
#define VERSION_MINOR 7
#define VERSION_PATCH 32
#define VERSION_PATCH 33
// Relay metadata (authoritative source for NIP-11 information)
#define RELAY_NAME "C-Relay"

View File

@@ -664,6 +664,38 @@ int broadcast_event_to_subscriptions(cJSON* event) {
return broadcasts;
}
// Check if any active subscription exists for a specific event kind (thread-safe)
int has_subscriptions_for_kind(int event_kind) {
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
subscription_t* sub = g_subscription_manager.active_subscriptions;
while (sub) {
if (sub->active && sub->filters) {
subscription_filter_t* filter = sub->filters;
while (filter) {
// Check if this filter includes our event kind
if (filter->kinds && cJSON_IsArray(filter->kinds)) {
cJSON* kind_item = NULL;
cJSON_ArrayForEach(kind_item, filter->kinds) {
if (cJSON_IsNumber(kind_item)) {
int filter_kind = (int)cJSON_GetNumberValue(kind_item);
if (filter_kind == event_kind) {
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
return 1; // Found matching subscription
}
}
}
}
filter = filter->next;
}
}
sub = sub->next;
}
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
return 0; // No matching subscriptions
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -118,4 +118,7 @@ void log_subscription_disconnected(const char* client_ip);
void log_event_broadcast(const char* event_id, const char* sub_id, const char* client_ip);
void update_subscription_events_sent(const char* sub_id, int events_sent);
// Subscription query functions
int has_subscriptions_for_kind(int event_kind);
#endif // SUBSCRIPTIONS_H