Auf Wunsch von Filipe. Betrifft ausschliesslich die Anzeige.
Der Rollenschluessel bleibt "admin". Er steckt in der CHECK-Regel der
Datenbank, in jeder bestehenden Sitzung und in jeder Rechteabfrage --
ihn umzubenennen haette alle drei gebrochen, und bestehende Anmeldungen
waeren ungueltig geworden. Umbenannt wird nur, was man LIEST.
Der Anzeigename steht jetzt an EINER Stelle (ROLLEN_NAME in
workspace.js) und kommt ueber /workspace/api/ich als rolle_name mit.
Stand er in elf Dateien, waere er beim naechsten Mal in zehn davon
geaendert.
Dabei aufgefallen: In der Kopfleiste stand auf JEDER Seite der rohe
Rollenschluessel -- "Chef · admin". Das war schon vorher unschoen, faellt
aber jetzt erst richtig auf. Alle elf Seiten zeigen jetzt "Chef ·
DogFather". Geprueft: kein rohes "admin" mehr in der Oberflaeche.
Geaendert: Rollenkachel und Untertitel auf der Anmeldeseite, Kopfleiste
aller Seiten, Rollentext auf dem Dashboard, Rollenmarken und Auswahl in
der Personenverwaltung, die Betreuungs-Auswahl ("nur DogFather"), der
Hinweis auf der Dateienseite, die Erklaerung zur internen Notiz im
Profil, die Hinweise fuer Scouts ohne Zuteilung -- und vier
Fehlermeldungen vom Server.
Schreibweise "DogFather" wie von Filipe geschrieben; das ist auch auf
der oeffentlichen Website die haeufigste Form (717 von 1216).
In den Code-Kommentaren der Fachmodule heisst die Rolle weiterhin "das
Management". Das bleibt bewusst so -- eine Massenaenderung an vierzig
Kommentaren waere reines Risiko ohne sichtbaren Nutzen. Ein Hinweis an
der ROLLEN_NAME-Zuordnung erklaert den Zusammenhang.
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 = 'nur 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();
|
|
})();
|
|
})();
|