const MAX_HISTORY = 50; let servers = []; let activeTabs = {}; let tabCounter = 0; let currentTheme = 'terminal'; const themes = { terminal: 'styles/terminal.css', dark: 'styles/dark.css', light: 'styles/light.css' }; document.addEventListener('DOMContentLoaded', async () => { servers = await window.rcon.getServers(); setupEventListeners(); setupThemeButtons(); renderServerList(); servers.forEach(s => openServer(s.id)); }); let inputModalCallback = null; function setupEventListeners() { document.getElementById('btn-add-server').addEventListener('click', showAddServerModal); document.getElementById('btn-servers').addEventListener('click', showServersModal); document.getElementById('btn-cancel-server').addEventListener('click', hideAddServerModal); document.getElementById('btn-close-servers').addEventListener('click', hideServersModal); document.getElementById('btn-close-about').addEventListener('click', hideAboutModal); document.getElementById('btn-about').addEventListener('click', showAboutModal); document.getElementById('btn-close-reference').addEventListener('click', hideReferenceModal); document.getElementById('btn-reference').addEventListener('click', showReferenceModal); document.getElementById('form-server').addEventListener('submit', saveServer); document.getElementById('btn-cancel-input').addEventListener('click', hideInputModal); document.getElementById('btn-submit-input').addEventListener('click', submitInputModal); document.getElementById('modal-input-field').addEventListener('keydown', (e) => { if (e.key === 'Enter') submitInputModal(); if (e.key === 'Escape') hideInputModal(); }); } function setupThemeButtons() { document.querySelectorAll('.theme-btn').forEach(btn => { btn.addEventListener('click', () => { setTheme(btn.dataset.theme); }); }); setTheme(currentTheme); } function setTheme(theme) { currentTheme = theme; document.getElementById('theme-stylesheet').href = themes[theme]; document.querySelectorAll('.theme-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.theme === theme); }); } function showAddServerModal(server = null) { const modal = document.getElementById('modal-add-server'); document.getElementById('server-id').value = server?.id || ''; document.getElementById('server-name').value = server?.name || ''; document.getElementById('server-address').value = server?.address || ''; document.getElementById('server-port').value = server?.port || 27960; document.getElementById('server-password').value = server?.password || ''; modal.classList.add('active'); } function hideAddServerModal() { document.getElementById('modal-add-server').classList.remove('active'); } function showServersModal() { renderServerList(); document.getElementById('modal-servers').classList.add('active'); } function hideServersModal() { document.getElementById('modal-servers').classList.remove('active'); } function showAboutModal() { document.getElementById('modal-about').classList.add('active'); } function hideAboutModal() { document.getElementById('modal-about').classList.remove('active'); } function showReferenceModal() { document.getElementById('modal-reference').classList.add('active'); } function hideReferenceModal() { document.getElementById('modal-reference').classList.remove('active'); } function showInputModal(title, placeholder, callback) { inputModalCallback = callback; document.getElementById('modal-input-title').textContent = title; document.getElementById('modal-input-field').placeholder = placeholder; document.getElementById('modal-input-field').value = ''; document.getElementById('modal-input').classList.add('active'); document.getElementById('modal-input-field').focus(); } function hideInputModal() { document.getElementById('modal-input').classList.remove('active'); inputModalCallback = null; } function submitInputModal() { const value = document.getElementById('modal-input-field').value; document.getElementById('modal-input').classList.remove('active'); if (inputModalCallback) { const cb = inputModalCallback; inputModalCallback = null; cb(value); } } async function saveServer(e) { e.preventDefault(); const server = { id: document.getElementById('server-id').value || undefined, name: document.getElementById('server-name').value, address: document.getElementById('server-address').value, port: parseInt(document.getElementById('server-port').value), password: document.getElementById('server-password').value }; servers = await window.rcon.saveServer(server); hideAddServerModal(); renderServerList(); const saved = servers.find(s => s.name === server.name && s.address === server.address); if (saved && !activeTabs[saved.id]) { openServer(saved.id); } } async function deleteServer(id) { if (confirm('Delete this server?')) { servers = await window.rcon.deleteServer(id); closeTab(id); renderServerList(); } } function renderServerList() { const list = document.getElementById('server-list'); if (servers.length === 0) { list.innerHTML = '

No servers configured.

'; return; } list.innerHTML = servers.map(s => `
${escapeHtml(s.name)} ${escapeHtml(s.address)}:${s.port}
`).join(''); } function openServer(id) { const server = servers.find(s => s.id === id); if (!server) return; if (!activeTabs[id]) { activeTabs[id] = createTab(server); tabCounter++; } switchToTab(id); hideServersModal(); } function createTab(server) { const id = server.id; const tab = { id, name: server.name, server, history: [], historyIndex: -1 }; const tabEl = document.createElement('div'); tabEl.className = 'tab'; tabEl.id = `tab-${id}`; tabEl.innerHTML = ` ${escapeHtml(server.name)} × `; tabEl.addEventListener('click', () => switchToTab(id)); const contentEl = document.createElement('div'); contentEl.className = 'tab-content'; contentEl.id = `content-${id}`; contentEl.innerHTML = `

${escapeHtml(server.name)}

${escapeHtml(server.address)}:${server.port}
`; document.getElementById('tabs-bar').appendChild(tabEl); document.getElementById('server-content').appendChild(contentEl); const placeholder = document.getElementById('tabs-placeholder'); if (placeholder) placeholder.style.display = 'none'; tabEl.querySelector('.tab-close').addEventListener('click', (e) => { e.stopPropagation(); closeTab(id); }); contentEl.querySelector('.cmd-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') { sendCommand(id); } else if (e.key === 'ArrowUp') { navigateHistory(id, -1); } else if (e.key === 'ArrowDown') { navigateHistory(id, 1); } }); contentEl.querySelector('.cmd-send').addEventListener('click', () => sendCommand(id)); contentEl.querySelectorAll('.qa-btn[data-cmd]').forEach(btn => { btn.addEventListener('click', () => handleQuickAction(id, btn.dataset.cmd)); }); contentEl.querySelector('.qa-clear').addEventListener('click', () => { const output = document.getElementById(`output-${id}`); if (output) output.innerHTML = ''; }); return tab; } function switchToTab(id) { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); const tabEl = document.getElementById(`tab-${id}`); const contentEl = document.getElementById(`content-${id}`); if (tabEl && contentEl) { tabEl.classList.add('active'); contentEl.classList.add('active'); contentEl.querySelector('.cmd-input')?.focus(); } } function closeTab(id) { const tabEl = document.getElementById(`tab-${id}`); const contentEl = document.getElementById(`content-${id}`); if (tabEl) tabEl.remove(); if (contentEl) contentEl.remove(); delete activeTabs[id]; const remaining = Object.keys(activeTabs); if (remaining.length > 0) { switchToTab(remaining[remaining.length - 1]); } else { document.getElementById('server-content').innerHTML = `

Q3A RCON Dashboard

Select or add a server to begin.

`; const placeholder = document.getElementById('tabs-placeholder'); if (placeholder) placeholder.style.display = ''; } } async function sendCommand(tabId) { const input = document.getElementById(`cmd-${tabId}`); const output = document.getElementById(`output-${tabId}`); const command = input.value.trim(); if (!command || !activeTabs[tabId]) return; const tab = activeTabs[tabId]; tab.history.push(command); if (tab.history.length > MAX_HISTORY) tab.history.shift(); tab.historyIndex = tab.history.length; input.value = ''; appendOutput(tabId, `> ${command}`, 'command'); const result = await window.rcon.send({ address: tab.server.address, port: tab.server.port, password: tab.server.password, command }); if (result.success) { appendOutput(tabId, result.response || '(no response)', 'response'); } else { appendOutput(tabId, `Error: ${result.error}`, 'error'); } } async function handleQuickAction(tabId, action) { const tab = activeTabs[tabId]; if (!tab) return; let command = action; let promptTitle = null; let promptPlaceholder = null; switch (action) { case 'kick': promptTitle = 'Kick Player'; promptPlaceholder = 'Player name or slot number'; command = 'kick '; break; case 'say': promptTitle = 'Broadcast Message'; promptPlaceholder = 'Message to send to all players'; command = 'say '; break; case 'map': promptTitle = 'Change Map'; promptPlaceholder = 'Map name (e.g., q3dm1)'; command = 'map '; break; case 'banaddr': promptTitle = 'Ban IP Address'; promptPlaceholder = 'IP or IP/subnet (e.g., 192.168.1.100)'; command = 'banaddr '; break; case 'dumpuser': promptTitle = 'Dump User Info'; promptPlaceholder = 'Player name or slot number'; command = 'dumpuser '; break; default: break; } if (promptTitle) { showInputModal(promptTitle, promptPlaceholder, (value) => { if (!value) return; const fullCommand = command + value; document.getElementById(`cmd-${tabId}`).value = fullCommand; sendCommand(tabId); }); return; } document.getElementById(`cmd-${tabId}`).value = command; await sendCommand(tabId); } function navigateHistory(tabId, direction) { const tab = activeTabs[tabId]; if (!tab || tab.history.length === 0) return; tab.historyIndex += direction; if (tab.historyIndex < 0) tab.historyIndex = 0; if (tab.historyIndex >= tab.history.length) { tab.historyIndex = tab.history.length; document.getElementById(`cmd-${tabId}`).value = ''; return; } document.getElementById(`cmd-${tabId}`).value = tab.history[tab.historyIndex]; } function appendOutput(tabId, text, type = '') { const output = document.getElementById(`output-${tabId}`); if (!output) return; const lines = text.split('\n'); lines.forEach(line => { const div = document.createElement('div'); div.className = `output-line ${type}`; div.textContent = line; output.appendChild(div); }); output.scrollTop = output.scrollHeight; } function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } window.servers = servers; window.openServer = openServer; window.deleteServer = deleteServer; window.showAddServerModal = showAddServerModal;