// Admin-Panel-Logik. Auth laeuft ueber Cookie-Session; alle /api/admin/* sind geschuetzt. const $ = (id) => document.getElementById(id); async function api(path, opts = {}) { const res = await fetch(path, opts); let data = null; try { data = await res.json(); } catch {} return { ok: res.ok, status: res.status, data }; } function toast(msg) { const t = $('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(toast._t); toast._t = setTimeout(() => t.classList.remove('show'), 1800); } const esc = (s) => String(s == null ? '' : s); function fmt(iso) { try { return new Date(iso).toLocaleString('de-DE'); } catch { return iso; } } // ---------- Login ---------- function showLogin() { $('login').classList.remove('hidden'); $('panel').classList.add('hidden'); $('pw').focus(); } function showPanel() { $('login').classList.add('hidden'); $('panel').classList.remove('hidden'); loadPlayers(); } async function doLogin() { const password = $('pw').value; const { ok, status } = await api('/api/admin/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }), }); if (ok) { $('pw').value = ''; $('login-err').textContent = ''; showPanel(); } else $('login-err').textContent = status === 429 ? 'Zu viele Versuche — kurz warten.' : 'Falsches Passwort.'; } $('login-btn').addEventListener('click', doLogin); $('pw').addEventListener('keydown', (e) => { if (e.key === 'Enter') doLogin(); }); $('logout-btn').addEventListener('click', async () => { await api('/api/admin/logout', { method: 'POST' }); showLogin(); }); // ---------- Tabs ---------- document.querySelectorAll('.tabs button').forEach((btn) => { btn.addEventListener('click', () => { document.querySelectorAll('.tabs button').forEach((b) => b.classList.remove('active')); document.querySelectorAll('section.tab').forEach((s) => s.classList.remove('active')); btn.classList.add('active'); const tab = btn.dataset.tab; $('tab-' + tab).classList.add('active'); if (tab === 'players') loadPlayers(); if (tab === 'settings') loadSettings(); if (tab === 'images') loadImages(); }); }); // ---------- Spieler ---------- async function loadPlayers() { const { ok, data } = await api('/api/admin/players'); if (!ok) return showLogin(); const bans = new Set((data.bans || []).map((b) => String(b).toLowerCase())); const players = data.players || []; if (!players.length) { $('players-wrap').innerHTML = '
Noch keine Spieler erfasst.
'; return; } const rows = players.map((p) => { const banned = bans.has(String(p.name).toLowerCase()); const tr = document.createElement('tr'); tr.innerHTML = `Keine Bilder.
'; return; } list.forEach((img) => { const file = img.url.split('/').pop(); const div = document.createElement('div'); div.className = 'thumb'; const im = document.createElement('img'); im.src = img.url; im.alt = img.name; const fn = document.createElement('div'); fn.className = 'fn'; fn.textContent = file; const del = document.createElement('button'); del.className = 'danger'; del.textContent = 'Löschen'; del.style.width = '100%'; del.addEventListener('click', () => deleteImage(kind, file)); div.append(im, fn, del); el.appendChild(div); }); } async function deleteImage(kind, file) { if (!confirm(`"${file}" löschen?`)) return; const { ok } = await api(`/api/admin/images/${kind}/${encodeURIComponent(file)}`, { method: 'DELETE' }); if (ok) { toast('Gelöscht'); loadImages(); } else toast('Löschen fehlgeschlagen'); } document.querySelectorAll('button[data-up]').forEach((btn) => { btn.addEventListener('click', async () => { const kind = btn.dataset.up; const input = $(kind + '-file'); if (!input.files || !input.files[0]) return toast('Keine Datei gewählt'); const fd = new FormData(); fd.append('image', input.files[0]); const res = await fetch(`/api/admin/images/${kind}`, { method: 'POST', body: fd }); if (res.ok) { toast('Hochgeladen'); input.value = ''; loadImages(); } else { const d = await res.json().catch(() => ({})); toast(d.error || 'Upload fehlgeschlagen'); } }); }); // ---------- Rangliste ---------- document.querySelectorAll('button[data-reset]').forEach((btn) => { btn.addEventListener('click', async () => { const scope = btn.dataset.reset; if (!confirm(scope === 'all' ? 'WIRKLICH die komplette Rangliste leeren?' : 'Heutige Rangliste leeren?')) return; const { ok, data } = await api('/api/admin/reset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scope }) }); toast(ok ? `Geleert (${data.removed} Einträge)` : 'Fehler'); }); }); // ---------- Init ---------- (async () => { const { ok } = await api('/api/admin/me'); if (ok) showPanel(); else showLogin(); })();