diff --git a/includes/websocket.js b/includes/websocket.js index be2d7cb..d89f4f4 100644 --- a/includes/websocket.js +++ b/includes/websocket.js @@ -4,7 +4,7 @@ import { checkConnectionStatus, checkGeyserStatus, checkSftpStatus } from './sta import { apiRequest } from './api.js'; const clients = new Map(); -const staticEndpoints = ['log', 'website', 'map', 'my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'my-link', 'my-geyser-link', 'my-sftp', 'holesail-hashes']; +const staticEndpoints = ['log', 'website', 'map', 'my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'my-link', 'my-geyser-link', 'my-sftp', 'holesail-hashes', 'internal-sftp']; const dynamicEndpoints = ['hello', 'time', 'mod-list']; // Helper function to start Docker stats interval @@ -14,8 +14,8 @@ function startDockerStatsInterval(ws, client, user, docker) { return null; } - // Send initial stats immediately - (async () => { + // Send initial stats immediately + (async () => { try { const initialStats = await getContainerStats(docker, user); if (ws.readyState === ws.OPEN) { @@ -80,10 +80,12 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) { const container = docker.getContainer(client.user); const inspect = await container.inspect(); if (inspect.State.Status !== 'running') { + console.warn(`Container ${client.user} is not running for endpoint ${endpoint}`); ws.send(JSON.stringify({ type: endpoint, error: `Container ${client.user} is not running` })); return; } } catch (error) { + console.error(`Failed to check container status for ${endpoint} for ${client.user}:`, error.message); ws.send(JSON.stringify({ type: endpoint, error: `Failed to check container status: ${error.message}` })); return; } @@ -99,6 +101,12 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) { return; } + if (endpoint === 'internal-sftp' && client.cache['internal-sftp']) { + console.log(`Using cached internal-sftp data for ${client.user}`); + ws.send(JSON.stringify({ type: 'internal-sftp', data: client.cache['internal-sftp'] })); + return; + } + if (endpoint === 'holesail-hashes') { try { const [hashResponse, geyserHashResponse, sftpHashResponse] = await Promise.all([ @@ -132,82 +140,95 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) { return; } - const response = await apiRequest(`/${endpoint}`, client.apiKey); - if (!response.error) { - if (endpoint === 'time') client.cache['time'] = response; - if (endpoint === 'my-link-cache') { - client.cache['my-link-cache'] = response; - if (client.subscriptions.has('my-link-cache') && client.user !== 'Unknown') { - try { - const container = docker.getContainer(client.user); - const inspect = await container.inspect(); - if (inspect.State.Status === 'running' && response.hostname && response.port) { - const status = await checkConnectionStatus(response.hostname, response.port); - ws.send(JSON.stringify({ type: 'connection-status', data: { isOnline: status.isOnline } })); - } else { - ws.send(JSON.stringify({ type: 'connection-status', error: `Container ${client.user} is not running` })); + try { + const response = await apiRequest(`/${endpoint}`, client.apiKey); + if (!response.error) { + if (endpoint === 'time') client.cache['time'] = response; + if (endpoint === 'my-link-cache') { + client.cache['my-link-cache'] = response; + if (client.subscriptions.has('my-link-cache') && client.user !== 'Unknown') { + try { + const container = docker.getContainer(client.user); + const inspect = await container.inspect(); + if (inspect.State.Status === 'running' && response.hostname && response.port) { + const status = await checkConnectionStatus(response.hostname, response.port); + ws.send(JSON.stringify({ type: 'connection-status', data: { isOnline: status.isOnline } })); + } else { + ws.send(JSON.stringify({ type: 'connection-status', error: `Container ${client.user} is not running` })); + } + } catch (error) { + console.error(`Error checking connection status for ${client.user}:`, error.message); + ws.send(JSON.stringify({ type: 'connection-status', error: `Failed to check container status: ${error.message}` })); } - } catch (error) { - ws.send(JSON.stringify({ type: 'connection-status', error: `Failed to check container status: ${error.message}` })); } } - } - if (endpoint === 'my-geyser-cache') { - client.cache['my-geyser-cache'] = response; - if (client.subscriptions.has('my-geyser-cache') && client.user !== 'Unknown') { - try { - const container = docker.getContainer(client.user); - const inspect = await container.inspect(); - if (inspect.State.Status === 'running' && response.hostname && response.port) { - const status = await checkGeyserStatus(response.hostname, response.port); - ws.send(JSON.stringify({ type: 'geyser-status', data: { isOnline: status.isOnline } })); - } else { - ws.send(JSON.stringify({ type: 'geyser-status', error: `Container ${client.user} is not running` })); + if (endpoint === 'my-geyser-cache') { + client.cache['my-geyser-cache'] = response; + if (client.subscriptions.has('my-geyser-cache') && client.user !== 'Unknown') { + try { + const container = docker.getContainer(client.user); + const inspect = await container.inspect(); + if (inspect.State.Status === 'running' && response.hostname && response.port) { + const status = await checkGeyserStatus(response.hostname, response.port); + ws.send(JSON.stringify({ type: 'geyser-status', data: { isOnline: status.isOnline } })); + } else { + ws.send(JSON.stringify({ type: 'geyser-status', error: `Container ${client.user} is not running` })); + } + } catch (error) { + console.error(`Error checking geyser status for ${client.user}:`, error.message); + ws.send(JSON.stringify({ type: 'geyser-status', error: `Failed to check container status: ${error.message}` })); } - } catch (error) { - ws.send(JSON.stringify({ type: 'geyser-status', error: `Failed to check container status: ${error.message}` })); } } - } - if (endpoint === 'my-sftp-cache') { - client.cache['my-sftp-cache'] = response; - if (client.subscriptions.has('my-sftp-cache') && client.user !== 'Unknown') { - try { - const container = docker.getContainer(client.user); - const inspect = await container.inspect(); - const ipAddress = inspect.NetworkSettings.Networks?.minecraft_network?.IPAddress || 'N/A'; - if (inspect.State.Status === 'running' && response.hostname && response.port) { - const status = await checkSftpStatus(response.hostname, response.port); - ws.send(JSON.stringify({ - type: 'sftp-status', - data: { - isOnline: status.isOnline, + if (endpoint === 'my-sftp-cache') { + client.cache['my-sftp-cache'] = response; + if (client.subscriptions.has('my-sftp-cache') && client.user !== 'Unknown') { + try { + const container = docker.getContainer(client.user); + const inspect = await container.inspect(); + const ipAddress = inspect.NetworkSettings.Networks?.minecraft_network?.IPAddress || 'N/A'; + if (inspect.State.Status === 'running' && response.hostname && response.port) { + const status = await checkSftpStatus(response.hostname, response.port); + ws.send(JSON.stringify({ + type: 'sftp-status', + data: { + isOnline: status.isOnline, + ipAddress + } + })); + } else { + ws.send(JSON.stringify({ + type: 'sftp-status', + error: `Container ${client.user} is not running`, ipAddress - } - })); - } else { + })); + } + response.ipAddress = ipAddress; + } catch (error) { + console.error(`Error checking sftp status for ${client.user}:`, error.message); ws.send(JSON.stringify({ type: 'sftp-status', - error: `Container ${client.user} is not running`, - ipAddress + error: `Failed to check container status: ${error.message}` })); } - // Add IP address to my-sftp-cache response - response.ipAddress = ipAddress; - } catch (error) { - ws.send(JSON.stringify({ - type: 'sftp-status', - error: `Failed to check container status: ${error.message}` - })); } } + if (endpoint === 'internal-sftp') { + client.cache['internal-sftp'] = response; + } + if (ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ type: endpoint, data: response })); + } + } else { + console.error(`API error for /${endpoint}: ${response.error}`); + if (ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ type: endpoint, error: response.error })); + } } + } catch (error) { + console.error(`Error fetching /${endpoint} for ${client.user}:`, error.message); if (ws.readyState === ws.OPEN) { - ws.send(JSON.stringify({ type: endpoint, data: response })); - } - } else { - if (ws.readyState === ws.OPEN) { - ws.send(JSON.stringify({ type: endpoint, error: response.error })); + ws.send(JSON.stringify({ type: endpoint, error: `Failed to fetch ${endpoint}: ${error.message}` })); } } } @@ -229,12 +250,14 @@ async function manageStatusChecks(ws, client, user, docker) { }); if (!isRunning || user === 'Unknown') { - ['my-link-cache', 'my-geyser-cache', 'my-sftp-cache'].forEach((sub) => { + ['my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'internal-sftp'].forEach((sub) => { if (client.subscriptions.has(sub)) { - ws.send(JSON.stringify({ type: sub.replace('-cache', '-status'), error: `Container ${user} is not running or user unknown` })); + const statusType = sub.replace('-cache', '-status'); + console.warn(`Container ${user} is not running or user unknown for subscription ${sub}`); + ws.send(JSON.stringify({ type: statusType, error: `Container ${user} is not running or user unknown` })); } }); - if (!isRunning && (client.subscriptions.has('my-link-cache') || client.subscriptions.has('my-geyser-cache') || client.subscriptions.has('my-sftp-cache')) && user !== 'Unknown') { + if (!isRunning && (client.subscriptions.has('my-link-cache') || client.subscriptions.has('my-geyser-cache') || client.subscriptions.has('my-sftp-cache') || client.subscriptions.has('internal-sftp')) && user !== 'Unknown') { console.log(`Starting container status monitor for ${user}`); client.statusCheckMonitorInterval = setInterval(async () => { try { @@ -368,6 +391,20 @@ export function handleWebSocket(ws, req, docker) { console.log(`Received pong from client for user ${client.user || 'unknown'}`); }); + // Send internal-sftp data immediately on connection if subscribed + (async () => { + try { + if (client.subscriptions.has('internal-sftp')) { + await fetchAndSendUpdate(ws, 'internal-sftp', client, docker); + } + } catch (error) { + console.error('Error sending initial internal-sftp data:', error.message); + if (ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch initial internal-sftp data: ${error.message}` })); + } + } + })(); + ws.on('message', async (message) => { try { const data = JSON.parse(message.toString()); @@ -380,6 +417,18 @@ export function handleWebSocket(ws, req, docker) { }); console.log(`Client subscriptions: ${Array.from(client.subscriptions)}`); + // Send internal-sftp data immediately after subscription + if (client.subscriptions.has('internal-sftp')) { + try { + await fetchAndSendUpdate(ws, 'internal-sftp', client, docker); + } catch (error) { + console.error(`Error fetching internal-sftp data after subscription for ${client.user}:`, error.message); + if (ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch internal-sftp data: ${error.message}` })); + } + } + } + let hello = client.cache['hello'] || await apiRequest('/hello', client.apiKey); if (!client.cache['hello'] && !hello.error) client.cache['hello'] = hello; @@ -439,7 +488,7 @@ export function handleWebSocket(ws, req, docker) { client.intervals.push(setInterval(async () => { try { for (const endpoint of staticEndpoints) { - if (client.subscriptions.has(endpoint) && endpoint !== 'holesail-hashes') { + if (client.subscriptions.has(endpoint) && endpoint !== 'holesail-hashes' && endpoint !== 'internal-sftp') { await fetchAndSendUpdate(ws, endpoint, client, docker); } } @@ -448,6 +497,28 @@ export function handleWebSocket(ws, req, docker) { } }, parseInt(process.env.STATIC_ENDPOINTS_INTERVAL_MS, 10))); + // Add 10-minute interval for internal-sftp updates + if (client.subscriptions.has('internal-sftp')) { + const sftpIntervalId = setInterval(async () => { + try { + if (ws.readyState !== ws.OPEN) { + console.warn(`WebSocket not open (state: ${ws.readyState}) for ${user}, clearing internal-sftp interval ${sftpIntervalId}`); + clearInterval(sftpIntervalId); + client.intervals = client.intervals.filter(id => id !== sftpIntervalId); + return; + } + await fetchAndSendUpdate(ws, 'internal-sftp', client, docker); + } catch (error) { + console.error(`Error in internal-sftp interval for ${user}:`, error.message); + if (ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch internal-sftp data: ${error.message}` })); + } + } + }, 10 * 60 * 1000); // 10 minutes + client.intervals.push(sftpIntervalId); + console.log(`Internal SFTP interval ID ${sftpIntervalId} added for ${user}`); + } + if (client.subscriptions.has('list-players') && user !== 'Unknown') { try { const container = docker.getContainer(user); @@ -520,6 +591,18 @@ export function handleWebSocket(ws, req, docker) { console.log(`Starting docker logs stream for new user ${client.user}`); await streamContainerLogs(docker, ws, client.user, client); } + + // Refresh internal-sftp data for new user + if (client.subscriptions.has('internal-sftp')) { + try { + await fetchAndSendUpdate(ws, 'internal-sftp', client, docker); + } catch (error) { + console.error(`Error refreshing internal-sftp data for new user ${client.user}:`, error.message); + if (ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch internal-sftp data: ${error.message}` })); + } + } + } } } else if (data.type === 'request') { const { requestId, endpoint, method, body } = data; @@ -540,6 +623,7 @@ export function handleWebSocket(ws, req, docker) { response = client.user === 'Unknown' ? { error: 'User not identified' } : await createBackup(docker, client.user); } else { response = await apiRequest(endpoint, client.apiKey, method, body); + console.log(`API request response for ${endpoint}:`, response); } if (ws.readyState === ws.OPEN) { ws.send(JSON.stringify({ requestId, ...response })); @@ -696,6 +780,7 @@ export function handleWebSocket(ws, req, docker) { console.log('Processing refresh request'); delete client.cache['hello']; delete client.cache['time']; + delete client.cache['internal-sftp']; await Promise.all([ ...staticEndpoints.filter(e => client.subscriptions.has(e)).map(e => fetchAndSendUpdate(ws, e, client, docker)), ...dynamicEndpoints.filter(e => client.subscriptions.has(e)).map(e => fetchAndSendUpdate(ws, e, client, docker)), @@ -739,6 +824,9 @@ export function handleWebSocket(ws, req, docker) { if (client.subscriptions.has('my-sftp-cache')) { ws.send(JSON.stringify({ type: 'sftp-status', error: `Container ${client.user} is not running` })); } + if (client.subscriptions.has('internal-sftp')) { + ws.send(JSON.stringify({ type: 'internal-sftp', error: `Container ${client.user} is not running` })); + } } } catch (error) { console.error(`Error during refresh for ${client.user}:`, error.message); diff --git a/public/js/app.js b/public/js/app.js index 570815f..e4c39e6 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -28,11 +28,8 @@ document.addEventListener('DOMContentLoaded', () => { 'enable-status' ]; - - const sections = document.querySelectorAll('.section-bg'); - const observer = new IntersectionObserver((entries) => { entries.forEach((entry, index) => { if (entry.isIntersecting) { @@ -50,7 +47,6 @@ document.addEventListener('DOMContentLoaded', () => { observer.observe(section); }); - function throttle(fn, wait) { let lastTime = 0; return function (...args) { @@ -66,13 +62,6 @@ document.addEventListener('DOMContentLoaded', () => { const hamburger = document.querySelector('.hamburger'); const mobileNav = document.querySelector('[data-mobile-nav]'); const navLinks = mobileNav.querySelectorAll('a'); - sections.forEach(section => { - section.style.transform = 'translateY(30px)'; - section.style.opacity = '0'; - section.style.transition = 'transform 0.7s ease-out, opacity 0.7s ease-out'; - observer.observe(section); - }); - // Debounce function to prevent rapid clicks function debounce(fn, wait) { @@ -101,6 +90,7 @@ document.addEventListener('DOMContentLoaded', () => { hamburger.classList.remove('active'); } }); + const elements = { loginPage: document.getElementById('loginPage'), loginApiKey: document.getElementById('loginApiKey'), @@ -459,6 +449,7 @@ document.addEventListener('DOMContentLoaded', () => { fitAddon = null; } } + function connectWebSocket() { if (ws && ws.readyState === WebSocket.OPEN) { return; @@ -485,7 +476,8 @@ document.addEventListener('DOMContentLoaded', () => { 'my-sftp-cache', 'backup', 'sftp-status', - 'holesail-hashes' + 'holesail-hashes', + 'internal-sftp' ] })); responseTimeout = setTimeout(() => { @@ -498,6 +490,19 @@ document.addEventListener('DOMContentLoaded', () => { try { clearTimeout(responseTimeout); const message = JSON.parse(event.data); + // Handle internal-sftp message immediately to save credentials + if (message.type === 'internal-sftp' && message.data?.hostname && message.data?.port && message.data?.user && message.data?.password) { + sftpCredentials = { + hostname: message.data.hostname, + port: message.data.port, + user: message.data.user, + password: message.data.password + }; + if (!hasAttemptedSftpConnect) { + hasAttemptedSftpConnect = true; + connectSftp(); + } + } if (message.requestId && pendingRequests.has(message.requestId)) { const { resolve, reject, notification, key } = pendingRequests.get(message.requestId); pendingRequests.delete(message.requestId); @@ -610,39 +615,6 @@ document.addEventListener('DOMContentLoaded', () => { } } - function updateDockerUI(message) { - if (message.error) { - console.log('Docker error detected, setting status to Not Running'); - if (elements.serverStatus) elements.serverStatus.textContent = 'Not Running'; - toggleSections('Not Running'); - return; - } - - const memoryPercent = parseFloat(message.data?.memory?.percent) || 0; - if (state.memoryPercent !== memoryPercent && elements.memoryPercent && memoryMeter) { - memoryMeter.data.datasets[0].data = [memoryPercent, 100 - memoryPercent]; - memoryMeter.update(); - elements.memoryPercent.textContent = `${memoryPercent.toFixed(1)}%`; - state.memoryPercent = memoryPercent; - } - - const cpuPercent = parseFloat(message.data?.cpu) || 0; - if (state.cpuPercent !== cpuPercent && elements.cpuPercent && cpuMeter) { - const scaledCpuPercent = Math.min((cpuPercent / 600) * 100, 100); - cpuMeter.data.datasets[0].data = [scaledCpuPercent, 100 - scaledCpuPercent]; - cpuMeter.update(); - elements.cpuPercent.textContent = `${cpuPercent.toFixed(1)}%`; - state.cpuPercent = cpuPercent; - } - - const status = message.data?.status || 'Unknown'; - if (elements.serverStatus) { - elements.serverStatus.textContent = status; - state.serverStatus = status; - toggleSections(status); - } - } - function toggleSections(status) { const sections = document.querySelectorAll('.section-bg'); const serverStatusSection = document.querySelector('.section-bg[data-section="server-status"]'); @@ -653,7 +625,6 @@ document.addEventListener('DOMContentLoaded', () => { const stopBtn = elements.stopBtn; const restartBtn = elements.restartBtn; const sftpBrowserSection = elements.sftpBrowserSection; - // Menu items in desktop and mobile nav const refreshBtn = elements.refresh || document.getElementById('refresh'); const mobileRefreshBtn = elements.mobileRefresh || document.getElementById('mobileRefresh'); const backupBtn = elements.backupBtn; @@ -672,13 +643,11 @@ document.addEventListener('DOMContentLoaded', () => { } if (status.toLowerCase() !== 'running') { - // Hide all sections except Server Status sections.forEach(section => { if (section !== serverStatusSection) { section.classList.add('hidden'); } }); - // Hide control buttons if (editPropertiesBtn) { editPropertiesBtn.classList.add('hidden'); editPropertiesBtn.style.display = 'none'; @@ -701,7 +670,6 @@ document.addEventListener('DOMContentLoaded', () => { if (sftpBrowserSection) { sftpBrowserSection.style.display = 'none'; } - // Hide Refresh, Backup, and Logout buttons in desktop nav if (refreshBtn) { refreshBtn.classList.add('hidden'); refreshBtn.style.display = 'none'; @@ -714,7 +682,6 @@ document.addEventListener('DOMContentLoaded', () => { logoutBtn.classList.add('hidden'); logoutBtn.style.display = 'none'; } - // Hide Refresh, Backup, and Logout menu items in mobile nav if (mobileRefreshBtn && mobileRefreshBtn.parentElement) { mobileRefreshBtn.parentElement.classList.add('hidden'); } @@ -729,11 +696,9 @@ document.addEventListener('DOMContentLoaded', () => { state.hasShownStartNotification = true; } } else { - // Show all sections sections.forEach(section => { section.classList.remove('hidden'); }); - // Show control buttons if (editPropertiesBtn) { editPropertiesBtn.classList.remove('hidden'); editPropertiesBtn.style.display = ''; @@ -751,12 +716,11 @@ document.addEventListener('DOMContentLoaded', () => { } if (restartBtn) { restartBtn.disabled = false; - stopBtn.classList.remove('disabled-btn'); + restartBtn.classList.remove('disabled-btn'); } if (sftpBrowserSection) { sftpBrowserSection.style.display = 'block'; } - // Show Refresh, Backup, and Logout buttons in desktop nav if (refreshBtn) { refreshBtn.classList.remove('hidden'); refreshBtn.style.display = ''; @@ -769,7 +733,6 @@ document.addEventListener('DOMContentLoaded', () => { logoutBtn.classList.remove('hidden'); logoutBtn.style.display = ''; } - // Show Refresh, Backup, and Logout menu items in mobile nav if (mobileRefreshBtn && mobileRefreshBtn.parentElement) { mobileRefreshBtn.parentElement.classList.remove('hidden'); } @@ -840,21 +803,11 @@ document.addEventListener('DOMContentLoaded', () => { function updateSftpCacheUI(message) { if (message.data?.hostname && message.data?.port && message.data?.user && message.data?.password) { - sftpCredentials = { - hostname: state.user, - port: 22, - user: message.data.user, - password: message.data.password - }; const sftpLinkText = `${message.data.hostname}:${message.data.port}`; if (state.sftpLink !== sftpLinkText && elements.sftpLink) { elements.sftpLink.textContent = sftpLinkText; state.sftpLink = sftpLinkText; } - if (!hasAttemptedSftpConnect) { - hasAttemptedSftpConnect = true; - connectSftp(); - } } } @@ -931,12 +884,10 @@ document.addEventListener('DOMContentLoaded', () => { state.currentPlayers = players; const isSinglePlayer = players.length <= 1; - // Initialize state for tracking action button visibility if not already present state.actionButtonStates = state.actionButtonStates || {}; const playerListHtml = players.length > 0 ? players.map(player => { - // Default to hidden action buttons unless state says otherwise const isActionButtonsVisible = state.actionButtonStates[player] || false; return `
@@ -964,7 +915,6 @@ document.addEventListener('DOMContentLoaded', () => { elements.playerList.innerHTML = playerListHtml; state.playerList = playerListHtml; - // Remove existing event listeners to prevent duplication const toggleButtons = document.querySelectorAll('.toggle-actions'); const closeButtons = document.querySelectorAll('.close-actions'); toggleButtons.forEach(button => { @@ -974,7 +924,6 @@ document.addEventListener('DOMContentLoaded', () => { button.replaceWith(button.cloneNode(true)); }); - // Add event listeners for toggle actions document.querySelectorAll('.toggle-actions').forEach(button => { button.addEventListener('click', () => { const player = button.getAttribute('data-player').trim(); @@ -982,12 +931,11 @@ document.addEventListener('DOMContentLoaded', () => { if (actionDiv) { actionDiv.classList.remove('hidden'); button.classList.add('hidden'); - state.actionButtonStates[player] = true; // Update state + state.actionButtonStates[player] = true; } }); }); - // Add event listeners for close actions document.querySelectorAll('.close-actions').forEach(button => { button.addEventListener('click', () => { const player = button.getAttribute('data-player').trim(); @@ -996,7 +944,7 @@ document.addEventListener('DOMContentLoaded', () => { if (actionDiv && toggleButton) { actionDiv.classList.add('hidden'); toggleButton.classList.remove('hidden'); - state.actionButtonStates[player] = false; // Update state + state.actionButtonStates[player] = false; } }); }); @@ -1290,7 +1238,6 @@ document.addEventListener('DOMContentLoaded', () => { } function showMainContent() { - // Check if apiKey exists in localStorage const apiKey = localStorage.getItem('apiKey'); if (!apiKey) { console.error('API key not found in localStorage'); @@ -1309,7 +1256,6 @@ document.addEventListener('DOMContentLoaded', () => { elements.loginPage.classList.add('hidden'); elements.mainContent.classList.remove('hidden'); - // Verify buttons exist const logoutBtn = document.getElementById('logoutBtn'); const mobileLogoutBtn = document.getElementById('mobileLogoutBtn'); @@ -1325,10 +1271,8 @@ document.addEventListener('DOMContentLoaded', () => { console.error('Mobile logout button (#mobileLogoutBtn) not found'); } - // Remove any existing logout listeners to prevent duplicates document.removeEventListener('click', handleLogoutClick); - // Event delegation for logout buttons function handleLogoutClick(event) { if (event.target.id === 'logoutBtn' || event.target.id === 'mobileLogoutBtn') { console.log(`Logout button clicked: ${event.target.id}`); @@ -1910,6 +1854,7 @@ document.addEventListener('DOMContentLoaded', () => { elements.editPropertiesModal.appendChild(modal); } + function renderPropertiesList(properties, filter = '') { const propertiesList = elements.propertiesFields.querySelector('#propertiesList'); propertiesList.innerHTML = '';