Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78d484cfe0 |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
189
api/index.js
189
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
|
||||
// ================================
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user