Das "nur" war aus der Bauzeit uebrig, als der Eintrag noch beschrieb,
wer den Creator sieht ("nur das Management"). Als Auswahl neben "Sam"
und "Patrick" gehoert an die Stelle schlicht der Name -- die Liste
beantwortet die Frage "wer ist zustaendig", nicht "wer sieht mit".
Geprueft: Zuordnung setzen, entfernen und wieder setzen funktioniert
unveraendert.
Co-Authored-By: Claude Opus 5 <[email protected]>
237 lines
8.8 KiB
JavaScript
237 lines
8.8 KiB
JavaScript
/* ===================================================================
|
|
Personen und Zugangscodes verwalten. Nur für DogFather -- der Server
|
|
liefert allen anderen ohnehin 404 auf diese Schnittstellen.
|
|
=================================================================== */
|
|
(() => {
|
|
'use strict';
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
const ROLLENNAME = { admin: 'DogFather', creator: 'Creator', scout: 'Scout' };
|
|
let ich = null;
|
|
|
|
const el = (tag, klasse, text) => {
|
|
const k = document.createElement(tag);
|
|
if (klasse) k.className = klasse;
|
|
if (text !== undefined) k.textContent = text;
|
|
return k;
|
|
};
|
|
|
|
const melde = (t) => { $('fehler').textContent = t || ''; };
|
|
|
|
async function hole(pfad, optionen = {}) {
|
|
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
|
|
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
|
|
if (a.status === 404 && pfad.includes('/verwaltung/')) {
|
|
location.assign('/workspace/start.html'); throw new Error('nicht erlaubt');
|
|
}
|
|
return a;
|
|
}
|
|
|
|
const datum = (iso) => (iso ? iso.slice(0, 16).replace('T', ' ') : '—');
|
|
|
|
/* ---------- Code anzeigen -------------------------------------------- */
|
|
|
|
function zeigeCode(p) {
|
|
$('code-name').textContent = `${p.name} · ${ROLLENNAME[p.rolle] || p.rolle}`;
|
|
$('code-wert').textContent = p.code;
|
|
$('code-kasten').hidden = false;
|
|
$('code-kasten').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
|
|
$('code-schliessen').addEventListener('click', () => {
|
|
$('code-kasten').hidden = true;
|
|
/* Den Code auch aus dem Dokument entfernen, damit er nicht in einer
|
|
Bildschirmaufnahme oder im Verlauf hängen bleibt. */
|
|
$('code-wert').textContent = '';
|
|
});
|
|
|
|
$('code-kopieren').addEventListener('click', async () => {
|
|
try {
|
|
await navigator.clipboard.writeText($('code-wert').textContent);
|
|
$('code-kopieren').textContent = 'Kopiert ✓';
|
|
setTimeout(() => { $('code-kopieren').textContent = 'Kopieren'; }, 1800);
|
|
} catch {
|
|
melde('Kopieren ging nicht — bitte von Hand markieren.');
|
|
}
|
|
});
|
|
|
|
/* ---------- Liste ----------------------------------------------------- */
|
|
|
|
let betreuerListe = [];
|
|
|
|
function zeile(p) {
|
|
const k = el('div', 'person');
|
|
k.dataset.aktiv = p.aktiv ? 'ja' : 'nein';
|
|
|
|
const links = el('div');
|
|
links.append(el('div', 'person__name', p.name + (p.id === ich.id ? ' (du)' : '')));
|
|
|
|
const z = el('div', 'person__zeile');
|
|
const rolle = el('span', 'marke-rolle', ROLLENNAME[p.rolle] || p.rolle);
|
|
rolle.dataset.rolle = p.rolle;
|
|
z.append(rolle);
|
|
if (!p.aktiv) z.append(el('span', null, 'gesperrt'));
|
|
z.append(el('span', null, `letzte Anmeldung: ${datum(p.letzter_login)}`));
|
|
z.append(el('span', null, `offene Aufgaben: ${p.offene_aufgaben}`));
|
|
if (p.sitzungen) z.append(el('span', null, `${p.sitzungen} offene Sitzung(en)`));
|
|
if (p.rolle === 'scout' && p.betreut_anzahl) {
|
|
z.append(el('span', null, `betreut ${p.betreut_anzahl} Creator`));
|
|
}
|
|
links.append(z);
|
|
|
|
/* Zuständigkeit. Steht nur bei Creator und nur dann, wenn es
|
|
überhaupt einen Scout gibt, dem man sie geben könnte. */
|
|
if (p.rolle === 'creator' && betreuerListe.length) {
|
|
const b = el('div', 'betreuung');
|
|
const s = el('label', 'betreuung__schild', 'Betreut von');
|
|
s.htmlFor = 'betreuer-' + p.id;
|
|
const w = el('select', 'betreuung__wahl');
|
|
w.id = 'betreuer-' + p.id;
|
|
const leer = document.createElement('option');
|
|
leer.value = ''; leer.textContent = 'DogFather';
|
|
w.append(leer);
|
|
for (const s2 of betreuerListe) {
|
|
const o = document.createElement('option');
|
|
o.value = String(s2.id); o.textContent = s2.name;
|
|
w.append(o);
|
|
}
|
|
w.value = p.betreuer_id ? String(p.betreuer_id) : '';
|
|
w.addEventListener('change', async () => {
|
|
w.disabled = true; melde('');
|
|
try {
|
|
const a = await hole(`/workspace/api/verwaltung/betreuung/${p.id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ betreuer_id: w.value || null }),
|
|
});
|
|
if (!a.ok) { melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.'); return; }
|
|
await laden();
|
|
} catch { /* umgeleitet */ } finally { w.disabled = false; }
|
|
});
|
|
b.append(s, w);
|
|
links.append(b);
|
|
}
|
|
|
|
const knoepfe = el('div', 'person__knoepfe');
|
|
knoepfe.append(tuKnopf('Neuer Code', async () => {
|
|
const eigen = p.id === ich.id;
|
|
const frage = eigen
|
|
? `Neuer Code für dich selbst?\n\nDeine aktuelle Sitzung wird sofort beendet und du musst dich mit dem neuen Code neu anmelden.`
|
|
: `Neuer Code für ${p.name}?\n\nDer alte Code gilt dann nicht mehr, offene Sitzungen werden beendet.`;
|
|
if (!confirm(frage)) return;
|
|
const a = await hole(`/workspace/api/verwaltung/personen/${p.id}/code`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(eigen ? { auch_mich: true } : {}),
|
|
});
|
|
if (!a.ok) { melde('Neuer Code hat nicht geklappt.'); return; }
|
|
zeigeCode(await a.json());
|
|
await laden();
|
|
}));
|
|
|
|
if (p.id !== ich.id) {
|
|
knoepfe.append(tuKnopf(p.aktiv ? 'Sperren' : 'Entsperren', async () => {
|
|
if (p.aktiv && !confirm(`${p.name} sperren?\n\nOffene Sitzungen werden sofort beendet.`)) return;
|
|
const a = await hole(`/workspace/api/verwaltung/personen/${p.id}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ aktiv: !p.aktiv }),
|
|
});
|
|
if (!a.ok) { melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.'); return; }
|
|
await laden();
|
|
}));
|
|
}
|
|
|
|
k.append(links, knoepfe);
|
|
return k;
|
|
}
|
|
|
|
function tuKnopf(text, tu) {
|
|
const b = el('button', 'schritt', text);
|
|
b.type = 'button';
|
|
b.addEventListener('click', async () => {
|
|
b.disabled = true; melde('');
|
|
try { await tu(); } catch { /* umgeleitet */ } finally { b.disabled = false; }
|
|
});
|
|
return b;
|
|
}
|
|
|
|
async function laden() {
|
|
const ziel = $('liste');
|
|
try {
|
|
const a = await hole('/workspace/api/verwaltung/personen');
|
|
if (!a.ok) { melde('Personen konnten nicht geladen werden.'); return; }
|
|
const { personen, betreuer } = await a.json();
|
|
betreuerListe = betreuer || [];
|
|
ziel.textContent = '';
|
|
ziel.setAttribute('aria-busy', 'false');
|
|
for (const p of personen) ziel.append(zeile(p));
|
|
await protokollLaden();
|
|
} catch { /* umgeleitet */ }
|
|
}
|
|
|
|
async function protokollLaden() {
|
|
try {
|
|
const a = await hole('/workspace/api/verwaltung/protokoll?anzahl=25');
|
|
if (!a.ok) return;
|
|
const { eintraege } = await a.json();
|
|
const ziel = $('protokoll');
|
|
ziel.textContent = '';
|
|
for (const e of eintraege) {
|
|
const z = el('div', 'protokoll-zeile');
|
|
z.append(el('span', 'protokoll-zeit', datum(e.zeitpunkt)));
|
|
z.append(el('span', null, e.aktion));
|
|
z.append(el('span', null, [e.wer, e.detail].filter(Boolean).join(' · ')));
|
|
z.append(el('span', 'protokoll-ip', e.ip || ''));
|
|
ziel.append(z);
|
|
}
|
|
} catch { /* umgeleitet */ }
|
|
}
|
|
|
|
/* ---------- Anlegen ---------------------------------------------------- */
|
|
|
|
const umschalten = (auf) => {
|
|
$('neu').hidden = !auf; $('neu-oeffnen').hidden = auf;
|
|
if (auf) $('f-name').focus();
|
|
};
|
|
$('neu-oeffnen').addEventListener('click', () => umschalten(true));
|
|
$('neu-abbrechen').addEventListener('click', () => {
|
|
$('neu').reset(); $('neu-fehler').textContent = ''; umschalten(false);
|
|
});
|
|
|
|
$('neu').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
$('neu-fehler').textContent = '';
|
|
const knopf = $('neu-speichern');
|
|
knopf.disabled = true;
|
|
try {
|
|
const a = await hole('/workspace/api/verwaltung/personen', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: $('f-name').value, rolle: $('f-rolle').value }),
|
|
});
|
|
if (!a.ok) {
|
|
$('neu-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.';
|
|
return;
|
|
}
|
|
$('neu').reset(); umschalten(false);
|
|
zeigeCode(await a.json());
|
|
await laden();
|
|
} catch { /* umgeleitet */ } finally { knopf.disabled = false; }
|
|
});
|
|
|
|
/* ---------- Start ------------------------------------------------------- */
|
|
|
|
|
|
(async () => {
|
|
try {
|
|
const a = await hole('/workspace/api/ich');
|
|
if (!a.ok) { location.assign('/workspace/'); return; }
|
|
ich = await a.json();
|
|
} catch { return; }
|
|
if (ich.rolle !== 'admin') { location.assign('/workspace/start.html'); return; }
|
|
$('wer').textContent = `${ich.name} · ${ich.rolle_name || ich.rolle}`;
|
|
await laden();
|
|
})();
|
|
})();
|