Compare commits

...

2 Commits

8 changed files with 331 additions and 39 deletions

View File

@@ -1109,3 +1109,91 @@ body.dark-mode .sql-results-table tbody tr:nth-child(even) {
border-radius: var(--border-radius);
box-sizing: border-box;
}
/* ================================
SIDE NAVIGATION MENU
================================ */
.side-nav {
position: fixed;
top: 0;
left: -300px;
width: 280px;
height: 100vh;
background: var(--secondary-color);
border-right: var(--border-width) solid var(--border-color);
z-index: 1000;
transition: left 0.3s ease;
overflow-y: auto;
padding-top: 80px;
}
.side-nav.open {
left: 0;
}
.side-nav-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: none;
}
.side-nav-overlay.show {
display: block;
}
.nav-menu {
list-style: none;
padding: 0;
margin: 0;
}
.nav-menu li {
border-bottom: var(--border-width) solid var(--muted-color);
}
.nav-menu li:last-child {
border-bottom: none;
}
.nav-item {
display: block;
padding: 15px 20px;
color: var(--primary-color);
text-decoration: none;
font-family: var(--font-family);
font-size: 16px;
font-weight: bold;
transition: all 0.2s ease;
cursor: pointer;
border: none;
background: none;
width: 100%;
text-align: left;
}
.nav-item:hover {
background: rgba(0, 0, 0, 0.05);
border-left: 4px solid var(--accent-color);
padding-left: 16px;
}
.nav-item.active {
background: rgba(255, 0, 0, 0.1);
border-left: 4px solid var(--accent-color);
padding-left: 16px;
}
.header-title.clickable {
cursor: pointer;
transition: all 0.2s ease;
}
.header-title.clickable:hover {
opacity: 0.8;
}

View File

@@ -9,11 +9,26 @@
</head>
<body>
<!-- Side Navigation Menu -->
<nav class="side-nav" id="side-nav">
<ul class="nav-menu">
<li><button class="nav-item" data-page="statistics">Statistics</button></li>
<li><button class="nav-item" data-page="subscriptions">Subscriptions</button></li>
<li><button class="nav-item" data-page="configuration">Configuration</button></li>
<li><button class="nav-item" data-page="authorization">Authorization</button></li>
<li><button class="nav-item" data-page="dm">DM</button></li>
<li><button class="nav-item" data-page="database">Database Query</button></li>
</ul>
</nav>
<!-- Side Navigation Overlay -->
<div class="side-nav-overlay" id="side-nav-overlay"></div>
<!-- Header with title and profile display -->
<div class="section">
<div class="header-content">
<div class="header-title">
<div class="header-title clickable" id="header-title">
<span class="relay-letter" data-letter="R">R</span>
<span class="relay-letter" data-letter="E">E</span>
<span class="relay-letter" data-letter="L">L</span>

View File

@@ -35,6 +35,10 @@ let statsAutoRefreshInterval = null;
let countdownInterval = null;
let countdownSeconds = 10;
// Side navigation state
let currentPage = 'statistics'; // Default page
let sideNavOpen = false;
// SQL Query state
let pendingSqlQueries = new Map();
@@ -484,36 +488,33 @@ function handleLogoutEvent() {
// Update visibility of admin sections based on login and relay connection status
function updateAdminSectionsVisibility() {
const divConfig = document.getElementById('div_config');
const authRulesSection = document.getElementById('authRulesSection');
const databaseStatisticsSection = document.getElementById('databaseStatisticsSection');
const subscriptionDetailsSection = document.getElementById('subscriptionDetailsSection');
const nip17DMSection = document.getElementById('nip17DMSection');
const sqlQuerySection = document.getElementById('sqlQuerySection');
const shouldShow = isLoggedIn && isRelayConnected;
if (divConfig) divConfig.style.display = shouldShow ? 'block' : 'none';
if (authRulesSection) authRulesSection.style.display = shouldShow ? 'block' : 'none';
if (databaseStatisticsSection) databaseStatisticsSection.style.display = shouldShow ? 'block' : 'none';
if (subscriptionDetailsSection) subscriptionDetailsSection.style.display = shouldShow ? 'block' : 'none';
if (nip17DMSection) nip17DMSection.style.display = shouldShow ? 'block' : 'none';
if (sqlQuerySection) sqlQuerySection.style.display = shouldShow ? 'block' : 'none';
// If logged in and connected, show the current page, otherwise hide all sections
if (shouldShow) {
// Show the current page
switchPage(currentPage);
// Start/stop auto-refresh based on visibility
if (shouldShow && databaseStatisticsSection && databaseStatisticsSection.style.display === 'block') {
// Load statistics immediately (no auto-refresh - using real-time monitoring events)
sendStatsQuery().catch(error => {
console.log('Auto-fetch statistics failed: ' + error.message);
});
// startStatsAutoRefresh(); // DISABLED - using real-time monitoring events instead
// Also load configuration and auth rules automatically when sections become visible
fetchConfiguration().catch(error => {
console.log('Auto-fetch configuration failed: ' + error.message);
});
loadAuthRules().catch(error => {
console.log('Auto-load auth rules failed: ' + error.message);
});
// Load data for the current page
loadCurrentPageData();
} else {
// Hide all sections when not logged in or not connected
const sections = [
'databaseStatisticsSection',
'subscriptionDetailsSection',
'div_config',
'authRulesSection',
'nip17DMSection',
'sqlQuerySection'
];
sections.forEach(sectionId => {
const section = document.getElementById(sectionId);
if (section) {
section.style.display = 'none';
}
});
stopStatsAutoRefresh();
}
@@ -521,6 +522,31 @@ function updateAdminSectionsVisibility() {
updateCountdownDisplay();
}
// Load data for the current page
function loadCurrentPageData() {
switch (currentPage) {
case 'statistics':
// Load statistics immediately (no auto-refresh - using real-time monitoring events)
sendStatsQuery().catch(error => {
console.log('Auto-fetch statistics failed: ' + error.message);
});
break;
case 'configuration':
// Load configuration
fetchConfiguration().catch(error => {
console.log('Auto-fetch configuration failed: ' + error.message);
});
break;
case 'authorization':
// Load auth rules
loadAuthRules().catch(error => {
console.log('Auto-load auth rules failed: ' + error.message);
});
break;
// Other pages don't need initial data loading
}
}
// Show login modal
function showLoginModal() {
if (loginModal && loginModalContainer) {
@@ -4556,6 +4582,85 @@ function initializeDarkMode() {
}
}
// Side navigation functions
function toggleSideNav() {
const sideNav = document.getElementById('side-nav');
const overlay = document.getElementById('side-nav-overlay');
if (sideNavOpen) {
sideNav.classList.remove('open');
overlay.classList.remove('show');
sideNavOpen = false;
} else {
sideNav.classList.add('open');
overlay.classList.add('show');
sideNavOpen = true;
}
}
function closeSideNav() {
const sideNav = document.getElementById('side-nav');
const overlay = document.getElementById('side-nav-overlay');
sideNav.classList.remove('open');
overlay.classList.remove('show');
sideNavOpen = false;
}
function switchPage(pageName) {
// Update current page
currentPage = pageName;
// Update navigation active state
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.classList.remove('active');
if (item.getAttribute('data-page') === pageName) {
item.classList.add('active');
}
});
// Hide all sections
const sections = [
'databaseStatisticsSection',
'subscriptionDetailsSection',
'div_config',
'authRulesSection',
'nip17DMSection',
'sqlQuerySection'
];
sections.forEach(sectionId => {
const section = document.getElementById(sectionId);
if (section) {
section.style.display = 'none';
}
});
// Show selected section
const pageMap = {
'statistics': 'databaseStatisticsSection',
'subscriptions': 'subscriptionDetailsSection',
'configuration': 'div_config',
'authorization': 'authRulesSection',
'dm': 'nip17DMSection',
'database': 'sqlQuerySection'
};
const targetSectionId = pageMap[pageName];
if (targetSectionId) {
const targetSection = document.getElementById(targetSectionId);
if (targetSection) {
targetSection.style.display = 'block';
}
}
// Close side navigation
closeSideNav();
log(`Switched to page: ${pageName}`, 'INFO');
}
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
console.log('C-Relay Admin API interface loaded');
@@ -4571,6 +4676,9 @@ document.addEventListener('DOMContentLoaded', () => {
initializeEventRateChart();
}, 1000); // Delay to ensure text_graph.js is loaded
// Initialize side navigation
initializeSideNavigation();
// Ensure admin sections are hidden by default on page load
updateAdminSectionsVisibility();
@@ -4581,6 +4689,35 @@ document.addEventListener('DOMContentLoaded', () => {
}, 100);
});
// Initialize side navigation event handlers
function initializeSideNavigation() {
// Header title click handler
const headerTitle = document.getElementById('header-title');
if (headerTitle) {
headerTitle.addEventListener('click', toggleSideNav);
}
// Overlay click handler
const overlay = document.getElementById('side-nav-overlay');
if (overlay) {
overlay.addEventListener('click', closeSideNav);
}
// Navigation item click handlers
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', (e) => {
const pageName = e.target.getAttribute('data-page');
if (pageName) {
switchPage(pageName);
}
});
});
// Set initial page
switchPage(currentPage);
}
// ================================
// SQL QUERY FUNCTIONS
// ================================

View File

@@ -1 +1 @@
3429782
3615986

View File

@@ -377,8 +377,8 @@ cJSON* query_subscription_details(void) {
return subscriptions_data;
}
// Generate and broadcast monitoring event
int generate_monitoring_event(void) {
// Generate event-driven monitoring events (triggered by event storage)
int generate_event_driven_monitoring(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");
@@ -403,22 +403,45 @@ int generate_monitoring_event(void) {
return -1;
}
// Generate CPU metrics monitoring event (also triggered by event storage)
if (generate_monitoring_event_for_type("cpu_metrics", query_cpu_metrics) != 0) {
DEBUG_ERROR("Failed to generate cpu_metrics monitoring event");
return -1;
}
DEBUG_INFO("Generated and broadcast event-driven monitoring events");
return 0;
}
// Generate subscription-driven monitoring events (triggered by subscription changes)
int generate_subscription_driven_monitoring(void) {
// Generate active_subscriptions monitoring event (subscription changes affect this)
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;
}
// Generate CPU metrics monitoring event
// Generate CPU metrics monitoring event (also triggered by subscription changes)
if (generate_monitoring_event_for_type("cpu_metrics", query_cpu_metrics) != 0) {
DEBUG_ERROR("Failed to generate cpu_metrics monitoring event");
return -1;
}
DEBUG_INFO("Generated and broadcast all monitoring events");
DEBUG_INFO("Generated and broadcast subscription-driven monitoring events");
return 0;
}
// Generate and broadcast monitoring event (legacy function - now calls event-driven version)
int generate_monitoring_event(void) {
return generate_event_driven_monitoring();
}
// 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
@@ -511,20 +534,42 @@ void monitoring_on_event_stored(void) {
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 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 monitoring events only when someone is listening
// Generate event-driven monitoring events only when someone is listening
last_monitoring_time = current_time;
generate_monitoring_event();
generate_event_driven_monitoring();
}
// Monitoring hook called when subscriptions change (create/close)
void monitoring_on_subscription_change(void) {
// 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 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 subscription-driven monitoring events only when someone is listening
last_monitoring_time = current_time;
generate_subscription_driven_monitoring();
}
// Forward declaration for known_configs (defined in config.c)

View File

@@ -61,6 +61,7 @@ int handle_sql_query_unified(cJSON* event, const char* query, char* error_messag
// Monitoring system functions
void monitoring_on_event_stored(void);
void monitoring_on_subscription_change(void);
int get_monitoring_throttle_seconds(void);
#endif // API_H

View File

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

View File

@@ -310,6 +310,9 @@ int add_subscription_to_manager(subscription_t* sub) {
// Log subscription creation to database (INSERT OR REPLACE handles duplicates)
log_subscription_created(sub);
// Trigger monitoring update for subscription changes
monitoring_on_subscription_change();
return 0;
}
@@ -357,6 +360,9 @@ int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) {
// Update events sent counter before freeing
update_subscription_events_sent(sub_id_copy, events_sent_copy);
// Trigger monitoring update for subscription changes
monitoring_on_subscription_change();
free_subscription(sub);
return 0;
}