diff --git a/api/index.css b/api/index.css index 3c0b747..e8cea37 100644 --- a/api/index.css +++ b/api/index.css @@ -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; +} diff --git a/api/index.html b/api/index.html index ca1f557..d6df5e8 100644 --- a/api/index.html +++ b/api/index.html @@ -9,11 +9,26 @@ + + + + +
+
-
+
R E L diff --git a/api/index.js b/api/index.js index 8d96b7c..7b2fac2 100644 --- a/api/index.js +++ b/api/index.js @@ -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 // ================================ diff --git a/src/main.h b/src/main.h index 102daa2..90b90c4 100644 --- a/src/main.h +++ b/src/main.h @@ -10,10 +10,10 @@ #define MAIN_H // Version information (auto-updated by build system) -#define VERSION "v0.7.35" +#define VERSION "v0.7.36" #define VERSION_MAJOR 0 #define VERSION_MINOR 7 -#define VERSION_PATCH 35 +#define VERSION_PATCH 36 // Relay metadata (authoritative source for NIP-11 information) #define RELAY_NAME "C-Relay"