v0.7.26 - Tidy up api

This commit is contained in:
Your Name
2025-10-18 14:48:16 -04:00
parent e312d7e18c
commit 48890a2121
19 changed files with 4340 additions and 113 deletions

665
src/api.c
View File

@@ -20,12 +20,537 @@
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nip017.h"
#include "../nostr_core_lib/nostr_core/nip044.h"
#include "subscriptions.h"
// External subscription manager (from main.c via subscriptions.c)
extern subscription_manager_t g_subscription_manager;
// Global variables for config change system
static pending_config_change_t* pending_changes_head = NULL;
static int pending_changes_count = 0;
#define CONFIG_CHANGE_TIMEOUT 300 // 5 minutes
// Forward declarations for database functions
int store_event(cJSON* event);
int broadcast_event_to_subscriptions(cJSON* event);
// Forward declarations for config functions
char* get_relay_private_key(void);
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;
// 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;
}
// Query event kind distribution from database
cJSON* query_event_kind_distribution(void) {
extern sqlite3* g_db;
if (!g_db) {
DEBUG_ERROR("Database not available for monitoring query");
return NULL;
}
// Query event kinds distribution with total count
sqlite3_stmt* stmt;
const char* sql = "SELECT kind, COUNT(*) as count FROM events GROUP BY kind ORDER BY count DESC";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare event kind distribution query");
return NULL;
}
cJSON* distribution = cJSON_CreateObject();
cJSON_AddStringToObject(distribution, "data_type", "event_kinds");
cJSON_AddNumberToObject(distribution, "timestamp", (double)time(NULL));
cJSON* kinds_array = cJSON_CreateArray();
long long total_events = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
int kind = sqlite3_column_int(stmt, 0);
long long count = sqlite3_column_int64(stmt, 1);
total_events += count;
cJSON* kind_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(kind_obj, "kind", kind);
cJSON_AddNumberToObject(kind_obj, "count", count);
cJSON_AddItemToArray(kinds_array, kind_obj);
}
sqlite3_finalize(stmt);
cJSON_AddNumberToObject(distribution, "total_events", total_events);
cJSON_AddItemToObject(distribution, "kinds", kinds_array);
return distribution;
}
// Query time-based statistics from database
cJSON* query_time_based_statistics(void) {
extern sqlite3* g_db;
if (!g_db) {
DEBUG_ERROR("Database not available for time stats query");
return NULL;
}
time_t now = time(NULL);
cJSON* time_stats = cJSON_CreateObject();
cJSON_AddStringToObject(time_stats, "data_type", "time_stats");
cJSON_AddNumberToObject(time_stats, "timestamp", (double)now);
cJSON* periods_array = cJSON_CreateArray();
// Define time periods: 24h, 7d, 30d
struct {
const char* period;
time_t seconds;
const char* description;
} periods[] = {
{"last_24h", 86400, "Events in the last 24 hours"},
{"last_7d", 604800, "Events in the last 7 days"},
{"last_30d", 2592000, "Events in the last 30 days"},
{NULL, 0, NULL}
};
// Get total events count
sqlite3_stmt* total_stmt;
const char* total_sql = "SELECT COUNT(*) FROM events";
long long total_events = 0;
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
total_events = sqlite3_column_int64(total_stmt, 0);
}
sqlite3_finalize(total_stmt);
}
// Query each time period
for (int i = 0; periods[i].period != NULL; i++) {
sqlite3_stmt* stmt;
const char* sql = "SELECT COUNT(*) FROM events WHERE created_at >= ?";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare time stats query");
continue;
}
time_t cutoff = now - periods[i].seconds;
sqlite3_bind_int64(stmt, 1, cutoff);
long long count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
cJSON* period_obj = cJSON_CreateObject();
cJSON_AddStringToObject(period_obj, "period", periods[i].period);
cJSON_AddNumberToObject(period_obj, "count", count);
cJSON_AddStringToObject(period_obj, "description", periods[i].description);
cJSON_AddItemToArray(periods_array, period_obj);
}
cJSON_AddItemToObject(time_stats, "periods", periods_array);
cJSON_AddNumberToObject(time_stats, "total_events", total_events);
return time_stats;
}
// Query top pubkeys by event count from database
cJSON* query_top_pubkeys(void) {
extern sqlite3* g_db;
if (!g_db) {
DEBUG_ERROR("Database not available for top pubkeys query");
return NULL;
}
// Query top 10 pubkeys by event count
sqlite3_stmt* stmt;
const char* sql = "SELECT pubkey, COUNT(*) as count FROM events GROUP BY pubkey ORDER BY count DESC LIMIT 10";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare top pubkeys query");
return NULL;
}
cJSON* top_pubkeys = cJSON_CreateObject();
cJSON_AddStringToObject(top_pubkeys, "data_type", "top_pubkeys");
cJSON_AddNumberToObject(top_pubkeys, "timestamp", (double)time(NULL));
cJSON* pubkeys_array = cJSON_CreateArray();
// Get total events count for percentage calculation
sqlite3_stmt* total_stmt;
const char* total_sql = "SELECT COUNT(*) FROM events";
long long total_events = 0;
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
total_events = sqlite3_column_int64(total_stmt, 0);
}
sqlite3_finalize(total_stmt);
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
long long count = sqlite3_column_int64(stmt, 1);
cJSON* pubkey_obj = cJSON_CreateObject();
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
cJSON_AddNumberToObject(pubkey_obj, "event_count", count);
// Percentage will be calculated by frontend using total_events
cJSON_AddItemToArray(pubkeys_array, pubkey_obj);
}
sqlite3_finalize(stmt);
cJSON_AddItemToObject(top_pubkeys, "pubkeys", pubkeys_array);
cJSON_AddNumberToObject(top_pubkeys, "total_events", total_events);
return top_pubkeys;
}
// Query active subscriptions from in-memory manager (NO DATABASE QUERY)
cJSON* query_active_subscriptions(void) {
// Access the global subscription manager
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
int total_subs = g_subscription_manager.total_subscriptions;
int max_subs = g_subscription_manager.max_total_subscriptions;
int max_per_client = g_subscription_manager.max_subscriptions_per_client;
// Calculate per-client statistics by iterating through active subscriptions
int client_count = 0;
int most_subs_per_client = 0;
// Count subscriptions per WebSocket connection
subscription_t* current = g_subscription_manager.active_subscriptions;
struct lws* last_wsi = NULL;
int current_client_subs = 0;
while (current) {
if (current->wsi != last_wsi) {
// New client
if (last_wsi != NULL) {
client_count++;
if (current_client_subs > most_subs_per_client) {
most_subs_per_client = current_client_subs;
}
}
last_wsi = current->wsi;
current_client_subs = 1;
} else {
current_client_subs++;
}
current = current->next;
}
// Handle last client
if (last_wsi != NULL) {
client_count++;
if (current_client_subs > most_subs_per_client) {
most_subs_per_client = current_client_subs;
}
}
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
// Calculate statistics
double utilization_percentage = max_subs > 0 ? (total_subs * 100.0 / max_subs) : 0.0;
double avg_subs_per_client = client_count > 0 ? (total_subs * 1.0 / client_count) : 0.0;
// Build JSON response matching the design spec
cJSON* subscriptions = cJSON_CreateObject();
cJSON_AddStringToObject(subscriptions, "data_type", "active_subscriptions");
cJSON_AddNumberToObject(subscriptions, "timestamp", (double)time(NULL));
cJSON* data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "total_subscriptions", total_subs);
cJSON_AddNumberToObject(data, "max_subscriptions", max_subs);
cJSON_AddNumberToObject(data, "utilization_percentage", utilization_percentage);
cJSON_AddNumberToObject(data, "subscriptions_per_client_avg", avg_subs_per_client);
cJSON_AddNumberToObject(data, "most_subscriptions_per_client", most_subs_per_client);
cJSON_AddNumberToObject(data, "max_subscriptions_per_client", max_per_client);
cJSON_AddNumberToObject(data, "active_clients", client_count);
cJSON_AddItemToObject(subscriptions, "data", data);
return subscriptions;
}
// Query detailed subscription information from in-memory manager (ADMIN ONLY)
cJSON* query_subscription_details(void) {
// Access the global subscription manager
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
time_t current_time = time(NULL);
cJSON* subscriptions_data = cJSON_CreateObject();
cJSON_AddStringToObject(subscriptions_data, "data_type", "subscription_details");
cJSON_AddNumberToObject(subscriptions_data, "timestamp", (double)current_time);
cJSON* data = cJSON_CreateObject();
cJSON* subscriptions_array = cJSON_CreateArray();
// Iterate through all active subscriptions
subscription_t* current = g_subscription_manager.active_subscriptions;
while (current) {
cJSON* sub_obj = cJSON_CreateObject();
// Basic subscription info
cJSON_AddStringToObject(sub_obj, "id", current->id);
cJSON_AddStringToObject(sub_obj, "client_ip", current->client_ip);
cJSON_AddNumberToObject(sub_obj, "created_at", (double)current->created_at);
cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)(current_time - current->created_at));
cJSON_AddNumberToObject(sub_obj, "events_sent", current->events_sent);
cJSON_AddBoolToObject(sub_obj, "active", current->active);
// Extract filter details
cJSON* filters_array = cJSON_CreateArray();
subscription_filter_t* filter = current->filters;
while (filter) {
cJSON* filter_obj = cJSON_CreateObject();
// Add kinds array if present
if (filter->kinds) {
cJSON_AddItemToObject(filter_obj, "kinds", cJSON_Duplicate(filter->kinds, 1));
}
// Add authors array if present
if (filter->authors) {
cJSON_AddItemToObject(filter_obj, "authors", cJSON_Duplicate(filter->authors, 1));
}
// Add ids array if present
if (filter->ids) {
cJSON_AddItemToObject(filter_obj, "ids", cJSON_Duplicate(filter->ids, 1));
}
// Add since/until timestamps if set
if (filter->since > 0) {
cJSON_AddNumberToObject(filter_obj, "since", (double)filter->since);
}
if (filter->until > 0) {
cJSON_AddNumberToObject(filter_obj, "until", (double)filter->until);
}
// Add limit if set
if (filter->limit > 0) {
cJSON_AddNumberToObject(filter_obj, "limit", filter->limit);
}
// Add tag filters if present
if (filter->tag_filters) {
cJSON_AddItemToObject(filter_obj, "tag_filters", cJSON_Duplicate(filter->tag_filters, 1));
}
cJSON_AddItemToArray(filters_array, filter_obj);
filter = filter->next;
}
cJSON_AddItemToObject(sub_obj, "filters", filters_array);
cJSON_AddItemToArray(subscriptions_array, sub_obj);
current = current->next;
}
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
// Add subscriptions array and count to data
cJSON_AddItemToObject(data, "subscriptions", subscriptions_array);
cJSON_AddNumberToObject(data, "total_count", cJSON_GetArraySize(subscriptions_array));
cJSON_AddItemToObject(subscriptions_data, "data", data);
return subscriptions_data;
}
// Generate and broadcast monitoring event
int generate_monitoring_event(void) {
// Generate event_kinds monitoring event
if (generate_monitoring_event_for_type("event_kinds", query_event_kind_distribution) != 0) {
DEBUG_ERROR("Failed to generate event_kinds monitoring event");
return -1;
}
// Generate time_stats monitoring event
if (generate_monitoring_event_for_type("time_stats", query_time_based_statistics) != 0) {
DEBUG_ERROR("Failed to generate time_stats monitoring event");
return -1;
}
// Generate top_pubkeys monitoring event
if (generate_monitoring_event_for_type("top_pubkeys", query_top_pubkeys) != 0) {
DEBUG_ERROR("Failed to generate top_pubkeys monitoring event");
return -1;
}
// Generate active_subscriptions monitoring event
if (generate_monitoring_event_for_type("active_subscriptions", query_active_subscriptions) != 0) {
DEBUG_ERROR("Failed to generate active_subscriptions monitoring event");
return -1;
}
// Generate subscription_details monitoring event (admin-only)
if (generate_monitoring_event_for_type("subscription_details", query_subscription_details) != 0) {
DEBUG_ERROR("Failed to generate subscription_details monitoring event");
return -1;
}
DEBUG_INFO("Generated and broadcast all monitoring events");
return 0;
}
// Helper function to generate monitoring event for a specific type
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void)) {
// Query the monitoring data
cJSON* monitoring_data = query_func();
if (!monitoring_data) {
DEBUG_ERROR("Failed to query monitoring data for %s", d_tag_value);
return -1;
}
// Convert to JSON string for content
char* content_json = cJSON_Print(monitoring_data);
cJSON_Delete(monitoring_data);
if (!content_json) {
DEBUG_ERROR("Failed to serialize monitoring data for %s", d_tag_value);
return -1;
}
// Get relay keys for signing
const char* relay_pubkey = get_config_value("relay_pubkey");
char* relay_privkey_hex = get_relay_private_key();
if (!relay_pubkey || !relay_privkey_hex) {
free(content_json);
DEBUG_ERROR("Could not get relay keys for monitoring event (%s)", d_tag_value);
return -1;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(relay_privkey_hex);
free(content_json);
DEBUG_ERROR("Failed to convert relay private key for monitoring event (%s)", d_tag_value);
return -1;
}
free(relay_privkey_hex);
// Create monitoring event (kind 34567)
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_AddStringToObject(monitoring_event, "content", content_json);
// Create tags array with d tag for identification
cJSON* tags = cJSON_CreateArray();
// d tag for event identification
cJSON* d_tag = cJSON_CreateArray();
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
cJSON_AddItemToArray(d_tag, cJSON_CreateString(d_tag_value));
cJSON_AddItemToArray(tags, d_tag);
cJSON_AddItemToObject(monitoring_event, "tags", tags);
// Use the library function to create and sign the event
cJSON* signed_event = nostr_create_and_sign_event(
34567, // kind
cJSON_GetStringValue(cJSON_GetObjectItem(monitoring_event, "content")), // content
tags, // tags
relay_privkey, // private key
(time_t)cJSON_GetNumberValue(cJSON_GetObjectItem(monitoring_event, "created_at")) // timestamp
);
if (!signed_event) {
cJSON_Delete(monitoring_event);
free(content_json);
DEBUG_ERROR("Failed to create and sign monitoring event (%s)", d_tag_value);
return -1;
}
// Replace the unsigned event with the signed one
cJSON_Delete(monitoring_event);
monitoring_event = signed_event;
// Broadcast the event to active subscriptions
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;
}
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()) {
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
}
// 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");
}
// Forward declaration for known_configs (defined in config.c)
typedef struct {
const char* key;
@@ -107,9 +632,10 @@ int broadcast_event_to_subscriptions(cJSON* event);
char* get_relay_private_key(void);
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);
// Forward declarations for database functions
int store_event(cJSON* event);
// Forward declaration for monitoring helper function
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void));
// Handle HTTP request for embedded files (assumes GET)
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
@@ -636,6 +1162,12 @@ char* generate_stats_json(void) {
}
cJSON_AddNumberToObject(response, "database_size_bytes", db_size);
// Get active subscriptions count from in-memory manager
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
int active_subs = g_subscription_manager.total_subscriptions;
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
cJSON_AddNumberToObject(response, "active_subscriptions", active_subs);
// Query total events count
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
@@ -914,6 +1446,11 @@ char* generate_stats_text(void) {
long long db_bytes = db_size ? (long long)cJSON_GetNumberValue(db_size) : 0;
double db_mb = db_bytes / (1024.0 * 1024.0);
// Get active subscriptions count from in-memory manager
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
int active_subs = g_subscription_manager.total_subscriptions;
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
// Format timestamps
char oldest_str[64] = "-";
char newest_str[64] = "-";
@@ -953,10 +1490,11 @@ char* generate_stats_text(void) {
"Metric\tValue\tDescription\n"
"Database Size\t%.2f MB (%lld bytes)\tCurrent database file size\n"
"Total Events\t%lld\tTotal number of events stored\n"
"Active Subscriptions\t%d\tCurrent active WebSocket subscriptions\n"
"Oldest Event\t%s\tTimestamp of oldest event\n"
"Newest Event\t%s\tTimestamp of newest event\n"
"\n",
db_mb, db_bytes, total, oldest_str, newest_str);
db_mb, db_bytes, total, active_subs, oldest_str, newest_str);
// Event Kind Distribution section
offset += snprintf(stats_text + offset, 16384 - offset,
@@ -1682,3 +2220,124 @@ int process_config_change_request(const char* admin_pubkey, const char* message)
free(change_id);
return 1; // Confirmation sent
}
// Handle monitoring system admin commands
int handle_monitoring_command(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi) {
if (!event || !command || !error_message) {
return -1;
}
// Get request event ID for response correlation
cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
snprintf(error_message, error_size, "Missing request event ID");
return -1;
}
const char* request_id = cJSON_GetStringValue(request_id_obj);
// Get sender pubkey for response
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
snprintf(error_message, error_size, "Missing sender pubkey");
return -1;
}
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
// Parse command
char cmd[256];
char arg[256];
cmd[0] = '\0';
arg[0] = '\0';
// Simple command parsing - split on space
const char* space_pos = strchr(command, ' ');
if (space_pos) {
size_t cmd_len = space_pos - command;
if (cmd_len < sizeof(cmd)) {
memcpy(cmd, command, cmd_len);
cmd[cmd_len] = '\0';
strcpy(arg, space_pos + 1);
}
} else {
strcpy(cmd, command);
}
// Convert to lowercase for case-insensitive matching
for (char* p = cmd; *p; p++) {
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) {
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);
}
char* endptr;
long throttle_seconds = strtol(arg, &endptr, 10);
if (*endptr != '\0' || throttle_seconds < 1 || throttle_seconds > 3600) {
char* response_content = "❌ Invalid throttle value\n\nThrottle must be between 1 and 3600 seconds.";
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
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) {
char response_content[256];
snprintf(response_content, sizeof(response_content),
"✅ Monitoring throttle updated\n\nMinimum interval between monitoring events: %ld seconds", 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();
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);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
}