📄 Source: dashboard.js
const API = 'api';
let currentServer = null;
let consoleTimer = null;
let statusTimer = null;
let playersTimer = null;
let whoami = { authed:false, lan:false, username:null, is_owner:false };
async function api(path, opts = {}) {
const res = await fetch(`${API}/${path}`, { credentials:'same-origin', ...opts });
const text = await res.text();
try { return JSON.parse(text); }
catch (e) { return { ok:false, error:'Bad JSON: ' + text.slice(0,200) }; }
}
async function post(path, data) {
return api(path, { method:'POST', body:new URLSearchParams(data) });
}
async function init() {
whoami = await api('whoami.php');
if (!whoami.authed) { showLogin(); return; }
showDashboard();
document.getElementById('who').textContent =
whoami.lan ? 'LAN (no login)' :
(whoami.username + (whoami.is_owner ? ' (owner)' : ''));
if (whoami.is_owner || whoami.lan) document.getElementById('usersBtn').style.display = '';
await refreshServers();
statusTimer = setInterval(refreshServers, 5000);
}
function showLogin(){ document.getElementById('loginScreen').style.display='';
document.getElementById('dashboard').style.display='none'; }
function showDashboard(){ document.getElementById('loginScreen').style.display='none';
document.getElementById('dashboard').style.display=''; }
async function doLogin(ev) {
ev.preventDefault();
const u = document.getElementById('loginUser').value;
const p = document.getElementById('loginPass').value;
const r = await post('login.php', { username:u, password:p });
if (!r.ok) { document.getElementById('loginError').textContent = r.error || 'Login failed'; return; }
location.reload();
}
async function doLogout(){ await post('logout.php', {}); location.reload(); }
async function refreshServers() {
const r = await api('status.php');
if (!r.ok) return;
const list = document.getElementById('serverList');
list.innerHTML = '';
r.servers.forEach(s => {
const li = document.createElement('li');
li.className = (s.name === currentServer) ? 'active' : '';
li.onclick = () => selectServer(s.name);
const left = document.createElement('div');
left.innerHTML = `<div class="name">${escapeHtml(s.name)}</div>
<div class="meta">${s.enabled === null ? 'no systemd' : (s.enabled ? 'auto-start on' : 'auto-start off')}</div>`;
const right = document.createElement('span');
right.className = 'dot ' + s.state;
right.title = s.state;
li.appendChild(left); li.appendChild(right); list.appendChild(li);
});
if (currentServer) updateControls(r.servers.find(s => s.name === currentServer));
}
function updateControls(s) {
if (!s) return;
document.getElementById('srvName').textContent = s.name;
document.getElementById('btnStart').disabled = (s.state === 'running');
document.getElementById('btnStop').disabled = (s.state !== 'running');
document.getElementById('btnRestart').disabled = (s.state !== 'running');
document.getElementById('btnEnable').disabled = (s.enabled === true || s.enabled === null);
document.getElementById('btnDisable').disabled = (s.enabled === false || s.enabled === null);
document.getElementById('srvState').className = 'dot ' + s.state;
}
function selectServer(name) {
currentServer = name;
document.querySelectorAll('.server-list li').forEach(li => li.classList.remove('active'));
refreshServers();
if (consoleTimer) clearInterval(consoleTimer);
if (playersTimer) clearInterval(playersTimer);
refreshConsole();
if (typeof refreshPlayers === 'function') refreshPlayers();
consoleTimer = setInterval(refreshConsole, 3000);
if (typeof refreshPlayers === 'function') playersTimer = setInterval(refreshPlayers, 5000);
}
async function refreshConsole() {
if (!currentServer) return;
const r = await api('console.php?server=' + encodeURIComponent(currentServer));
if (!r.ok) return;
const el = document.getElementById('console');
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 20;
el.textContent = r.console || '';
if (atBottom) el.scrollTop = el.scrollHeight;
}
async function sendCommand() {
if (!currentServer) return;
const input = document.getElementById('cmdInput');
const cmd = input.value.trim();
if (!cmd) return;
const r = await post('command.php', { server:currentServer, command:cmd });
if (!r.ok) alert(r.error || 'Failed');
else { input.value = ''; setTimeout(refreshConsole, 500); }
}
async function control(action) {
if (!currentServer) return;
if (action === 'stop' && !confirm(`Stop "${currentServer}"?\n\nPlayers will be disconnected. The world will be saved before shutdown.`)) return;
if (action === 'disable' && !confirm(`Disable auto-start for "${currentServer}"?\n\nIt will NOT start automatically on boot.`)) return;
const r = await post(action + '.php', { server:currentServer });
if (!r.ok) alert(r.error || r.message || 'Failed');
setTimeout(refreshServers, 800);
setTimeout(refreshConsole, 800);
}
async function openUsers() {
const r = await api('users.php');
if (!r.ok) { alert(r.error || 'Failed'); return; }
document.getElementById('usersBody').innerHTML = r.users.map(u => `
<tr>
<td>${escapeHtml(u.username)}</td>
<td>${u.is_owner ? 'owner' : 'user'}</td>
<td>${u.device_bound ? 'bound' : '—'}</td>
<td>
${u.is_owner ? '' : `<button onclick="deleteUser('${escapeHtml(u.username)}')">delete</button>`}
${u.device_bound ? `<button onclick="unbindUser('${escapeHtml(u.username)}')">unbind</button>` : ''}
</td>
</tr>`).join('');
document.getElementById('usersModal').classList.add('open');
}
function closeUsers(){ document.getElementById('usersModal').classList.remove('open'); }
async function createUser() {
const username = document.getElementById('newUser').value.trim();
const password = document.getElementById('newPass').value;
const bound = document.getElementById('newBound').checked;
const r = await post('users.php', { action:'create', username, password, device_bound: bound ? 1 : 0 });
if (!r.ok) { alert(r.error || 'Failed'); return; }
document.getElementById('newUser').value = '';
document.getElementById('newPass').value = '';
openUsers();
}
async function deleteUser(username) {
if (!confirm(`Delete user "${username}"?`)) return;
await post('users.php', { action:'delete', username });
openUsers();
}
async function unbindUser(username) {
if (!confirm(`Unbind device for "${username}"?`)) return;
await post('users.php', { action:'unbind', username });
openUsers();
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
}
window.addEventListener('DOMContentLoaded', init);
← Back