let socket; let autoScroll = true; let serverRunning = false; let isExternalServer = false; document.addEventListener('DOMContentLoaded', function() { // Initialize WebSocket connection for real-time updates initializeWebSocket(); // Load initial data loadWorlds(); updateServerStatus(); // Set up periodic status updates (every 3 seconds for better responsiveness) setInterval(updateServerStatus, 3000); // Add event listeners for buttons document.getElementById('startBtn').addEventListener('click', startServer); document.getElementById('stopBtn').addEventListener('click', stopServer); document.getElementById('restartBtn').addEventListener('click', restartServer); document.getElementById('downloadBtn').addEventListener('click', downloadLogs); document.getElementById('clearBtn').addEventListener('click', clearLogs); document.getElementById('autoScrollBtn').addEventListener('click', toggleAutoScroll); document.getElementById('sendBtn').addEventListener('click', sendCommand); // Add enter key handler for console input document.getElementById('consoleInput').addEventListener('keypress', function(event) { if (event.key === 'Enter') { sendCommand(); } }); }); function initializeWebSocket() { socket = io(); socket.on('server:log', function(logEntry) { addLogEntry(logEntry.type, logEntry.content, logEntry.timestamp); }); socket.on('server:status', function(status) { isExternalServer = status.isExternal || false; updateStatusDisplay(status); }); socket.on('server:players', function(players) { updatePlayersList(players, isExternalServer); }); } async function updateServerStatus() { try { const response = await fetch('/api/server/status'); // Check for authentication redirect const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { console.warn('Authentication required for server status'); // Silently fail for status updates, don't redirect automatically return; } if (!response.ok) { throw new Error('HTTP error! status: ' + response.status); } const status = await response.json(); isExternalServer = status.isExternal || false; updateStatusDisplay(status); } catch (error) { console.error('Failed to update server status:', error); } } async function checkServerStatus() { try { const response = await fetch('/api/server/status'); // Check for authentication redirect const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { return null; } if (!response.ok) { return null; } return await response.json(); } catch (error) { console.error('Failed to check server status:', error); return null; } } function updateStatusDisplay(status) { const statusLight = document.getElementById('statusLight'); const statusText = document.getElementById('statusText'); const startBtn = document.getElementById('startBtn'); const stopBtn = document.getElementById('stopBtn'); const restartBtn = document.getElementById('restartBtn'); const consoleInputGroup = document.getElementById('consoleInputGroup'); const wasRunning = serverRunning; serverRunning = status.isRunning; if (status.isRunning) { if (status.isReady) { // Server is running and ready to accept connections statusLight.className = 'status-light online'; statusText.textContent = status.isExternal ? 'Running (External - Monitor Only)' : 'Running'; } else { // Server process is running but not ready yet statusLight.className = 'status-light starting'; statusText.textContent = 'Starting...'; } // For external servers, disable control buttons if (status.isExternal) { startBtn.disabled = true; stopBtn.disabled = true; restartBtn.disabled = true; consoleInputGroup.style.display = 'none'; } else { startBtn.disabled = true; stopBtn.disabled = false; restartBtn.disabled = false; consoleInputGroup.style.display = 'block'; } } else { statusLight.className = 'status-light offline'; statusText.textContent = 'Offline'; startBtn.disabled = false; stopBtn.disabled = true; restartBtn.disabled = true; consoleInputGroup.style.display = 'none'; // Reset button states if server stopped unexpectedly if (startBtn.textContent === 'âŗ Starting...') { startBtn.textContent = 'âļī¸ Start Server'; } if (restartBtn.textContent === 'âŗ Restarting...') { restartBtn.textContent = 'đ Restart Server'; } // Log if server stopped unexpectedly if (wasRunning && !status.isRunning) { addLogEntry('warning', 'Server has stopped. Check logs for details.'); } } // Update stats document.getElementById('uptime').textContent = formatUptime(status.uptime); document.getElementById('playerCount').textContent = status.players || 0; document.getElementById('memoryUsage').textContent = status.memoryUsage ? Math.round(status.memoryUsage) + ' MB' : '--'; // Debug: Log the status to see what we're getting console.log('Server status update:', { isRunning: status.isRunning, players: status.players, uptime: status.uptime }); } function formatUptime(milliseconds) { if (!milliseconds) return '--'; const seconds = Math.floor(milliseconds / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return days + 'd ' + (hours % 24) + 'h'; if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm'; if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's'; return seconds + 's'; } async function loadWorlds() { console.log('loadWorlds() called'); try { const response = await fetch('/api/worlds'); // Check for authentication redirect const contentType = response.headers.get('content-type'); console.log('Response status:', response.status, 'Content-Type:', contentType); if (contentType && contentType.includes('text/html')) { console.warn('Authentication required for loading worlds'); document.getElementById('worldSelect').innerHTML = '' + '' + ''; return; } if (!response.ok) { throw new Error('HTTP error! status: ' + response.status); } const worlds = await response.json(); console.log('Worlds received:', worlds); const worldSelect = document.getElementById('worldSelect'); if (worlds.length === 0) { worldSelect.innerHTML = '' + '' + ''; } else { worldSelect.innerHTML = ''; worlds.forEach(world => { const option = document.createElement('option'); option.value = world.name; option.textContent = 'đ ' + (world.displayName || world.name); worldSelect.appendChild(option); }); } } catch (error) { console.error('Failed to load worlds:', error); document.getElementById('worldSelect').innerHTML = ''; } } async function startServer() { const worldName = document.getElementById('worldSelect').value; console.log('Starting server with world:', worldName); const startBtn = document.getElementById('startBtn'); // Validate that a world is selected if (!worldName) { addLogEntry('error', 'Please select a world before starting the server'); return; } try { startBtn.disabled = true; startBtn.textContent = 'âŗ Starting...'; const response = await fetch('/api/server/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ worldName: worldName }) }); // Check if response is HTML (redirect to login) instead of JSON const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { addLogEntry('error', 'Authentication required. Please refresh the page and log in.'); startBtn.disabled = false; startBtn.textContent = 'âļī¸ Start Server'; // Optionally redirect to login setTimeout(() => { window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname); }, 2000); return; } if (!response.ok) { const errorText = await response.text(); startBtn.disabled = false; startBtn.textContent = 'âļī¸ Start Server'; throw new Error('HTTP error! status: ' + response.status + ' - ' + errorText); } const result = await response.json(); if (result.success) { addLogEntry('info', result.message || 'Server started successfully'); await updateServerStatus(); // Monitor for early server crash setTimeout(async () => { const status = await checkServerStatus(); if (status && !status.isRunning) { addLogEntry('warning', 'Server appears to have stopped unexpectedly. Check logs for errors.'); startBtn.disabled = false; startBtn.textContent = 'âļī¸ Start Server'; } }, 3000); // Check after 3 seconds } else { addLogEntry('error', 'Failed to start server: ' + (result.error || 'Unknown error')); startBtn.disabled = false; startBtn.textContent = 'âļī¸ Start Server'; } } catch (error) { console.error('Server start error:', error); addLogEntry('error', 'Failed to start server: ' + error.message); startBtn.disabled = false; startBtn.textContent = 'âļī¸ Start Server'; } } async function stopServer() { const stopBtn = document.getElementById('stopBtn'); try { stopBtn.disabled = true; stopBtn.textContent = 'âŗ Stopping...'; const response = await fetch('/api/server/stop', { method: 'POST' }); // Check for authentication redirect const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { addLogEntry('error', 'Authentication required. Please refresh the page and log in.'); stopBtn.disabled = false; stopBtn.textContent = 'âšī¸ Stop Server'; setTimeout(() => { window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname); }, 2000); return; } if (!response.ok) { throw new Error('HTTP error! status: ' + response.status); } const result = await response.json(); if (result.success) { addLogEntry('info', result.message || 'Server stopped successfully'); await updateServerStatus(); } else { addLogEntry('error', 'Failed to stop server: ' + (result.error || 'Unknown error')); } } catch (error) { console.error('Server stop error:', error); addLogEntry('error', 'Failed to stop server: ' + error.message); } finally { stopBtn.disabled = false; stopBtn.textContent = 'âšī¸ Stop Server'; } } async function restartServer() { const worldName = document.getElementById('worldSelect').value; const restartBtn = document.getElementById('restartBtn'); // Validate that a world is selected if (!worldName) { addLogEntry('error', 'Please select a world before restarting the server'); return; } try { restartBtn.disabled = true; restartBtn.textContent = 'âŗ Restarting...'; const response = await fetch('/api/server/restart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ worldName: worldName || null }) }); // Check for authentication redirect const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { addLogEntry('error', 'Authentication required. Please refresh the page and log in.'); restartBtn.disabled = false; restartBtn.textContent = 'đ Restart Server'; setTimeout(() => { window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname); }, 2000); return; } if (!response.ok) { const errorText = await response.text(); restartBtn.disabled = false; restartBtn.textContent = 'đ Restart Server'; throw new Error('HTTP error! status: ' + response.status + ' - ' + errorText); } const result = await response.json(); if (result.success) { addLogEntry('info', result.message || 'Server restarted successfully'); await updateServerStatus(); // Monitor for early server crash setTimeout(async () => { const status = await checkServerStatus(); if (status && !status.isRunning) { addLogEntry('warning', 'Server appears to have stopped unexpectedly after restart. Check logs for errors.'); restartBtn.disabled = false; restartBtn.textContent = 'đ Restart Server'; } }, 3000); // Check after 3 seconds } else { addLogEntry('error', 'Failed to restart server: ' + (result.error || 'Unknown error')); } } catch (error) { console.error('Server restart error:', error); addLogEntry('error', 'Failed to restart server: ' + error.message); } finally { restartBtn.disabled = false; restartBtn.textContent = 'đ Restart Server'; } } function addLogEntry(type, message, timestamp) { const consoleContent = document.getElementById('consoleContent'); const logEntry = document.createElement('div'); timestamp = timestamp || new Date().toLocaleTimeString(); logEntry.className = 'log-entry ' + type; logEntry.innerHTML = '
' + ' '; consoleContent.appendChild(logEntry); // Auto-scroll to bottom if enabled if (autoScroll) { consoleContent.scrollTop = consoleContent.scrollHeight; } // Limit log entries to prevent memory issues const maxEntries = 1000; while (consoleContent.children.length > maxEntries) { consoleContent.removeChild(consoleContent.firstChild); } } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function clearLogs() { document.getElementById('consoleContent').innerHTML = ''; addLogEntry('info', 'Console cleared'); } function toggleAutoScroll() { autoScroll = !autoScroll; document.getElementById('autoScrollText').textContent = 'Auto-scroll: ' + (autoScroll ? 'ON' : 'OFF'); } async function sendCommand() { const input = document.getElementById('consoleInput'); const command = input.value.trim(); if (!command) return; try { const response = await fetch('/api/server/command', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command }) }); // Check for authentication redirect const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { addLogEntry('error', 'Authentication required. Please refresh the page and log in.'); setTimeout(() => { window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname); }, 2000); return; } if (!response.ok) { throw new Error('HTTP error! status: ' + response.status); } const result = await response.json(); if (result.success) { addLogEntry('info', 'Command sent: ' + command); input.value = ''; } else { addLogEntry('error', 'Failed to send command: ' + (result.error || 'Unknown error')); } } catch (error) { console.error('Send command error:', error); addLogEntry('error', 'Failed to send command: ' + error.message); } } function updatePlayersList(players, isExternal) { const playersList = document.getElementById('playersList'); if (!players || players.length === 0) { playersList.innerHTML = 'No players online
'; return; } // Create a table for better formatting with kick functionality const playersHtml = 'Player | ' + 'Last Activity | ' + 'Actions | ' + '
---|---|---|
' + escapeHtml(player.name) + ' | ' + '' +
'' + lastActivity + ' ' + '' + (player.lastAction || 'Active') + '' + ' | ' +
'' + '' + ' | ' + '