📂 File Browser

/serversdashboard/serversdashboard/assets
☀️ Light Mode
🎯 Quick Launch:

📁 Directories

📄 Files

📜 dashboard.js
▶ Open 📄 View Source
🎨 players.css
▶ Open 📄 View Source
📜 players.js
▶ Open 📄 View Source
🎨 style.css
▶ Open 📄 View Source

📄 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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
window.addEventListener('DOMContentLoaded', init);
← Back