// ----------------------------- // Admin — Rollen & Rechte // ----------------------------- // Eriks Wahl „B“ aus dem Entwurfsvergleich: links die Rollen (Anzahl Konten, // Anteil der Rechte), darunter „+ Neue Rolle“; rechts die gewählte Rolle mit // einem Schalter je Recht, nach Gruppen, und „alle/keine“ je Gruppe. Über // „Vergleich mit“ steht neben jedem Schalter, ob eine zweite Rolle das Recht // hat (Abweichungen hervorgehoben). // // Daten und Katalog kommen vom Server (GET /rollen, server.js „Rollen & // Rechte: Routen“); jede Änderung geht sofort hin (PUT /rollen/:key/rechte, // POST/PATCH/DELETE /rollen) und die Antwort ersetzt den Stand hier. Kein // eigenes Speichern. Ändert jemand seine eigene Rolle, lädt sich /whoami neu, // damit Seitenleiste und Knöpfe sofort stimmen. let rrDaten = null; // { katalog:[{gruppe, rechte:[{key,name,hinweis}]}], rollen:[…] } let rrAuswahl = 'member'; // gewählte Rolle let rrVergleich = ''; // Vergleichsrolle ('' = keiner) let rrNeuOffen = false; // Formular „+ Neue Rolle“ aufgeklappt let rrMeldung = ''; let rrMeldungTimer = null; // renderRollenRechte: Einstieg beim Öffnen des Abschnitts (showAdminSection). function renderRollenRechte(){ const el = document.getElementById('rollenRechte'); if(!el) return; if(!rrDaten) el.innerHTML = 'Lade Rollen…'; fetch(baseUrl() + '/rollen') .then(r => r.json()) .then(j => { if(!j.ok) throw new Error(j.error || 'Laden fehlgeschlagen'); rrUebernehmen(j); }) .catch(err => { el.innerHTML = `Rollen konnten nicht geladen werden: ${esc(err.message || err)}`; }); } // rrUebernehmen: neue Serverantwort übernehmen und neu zeichnen. function rrUebernehmen(j){ rrDaten = j; if(!j.rollen.some(r => r.key === rrAuswahl)) rrAuswahl = (j.rollen.find(r => !r.fest) || j.rollen[0] || {}).key; if(rrVergleich && !j.rollen.some(r => r.key === rrVergleich)) rrVergleich = ''; rrZeichnen(); } function rrMelden(text){ rrMeldung = text; const el = document.getElementById('rrMeldung'); if(el) el.textContent = text; clearTimeout(rrMeldungTimer); rrMeldungTimer = setTimeout(() => { rrMeldung = ''; const e = document.getElementById('rrMeldung'); if(e) e.textContent = ''; }, 2500); } // rrSenden: gemeinsamer Weg aller Änderungen. Parameter: methode, pfad, // body, okText. Rückgabe: Promise der Serverantwort (oder null bei Fehler). function rrSenden(methode, pfad, body, okText){ return fetch(baseUrl() + pfad, { method: methode, headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: body ? JSON.stringify(body) : undefined }) .then(r => r.json().then(j => ({ ok: r.ok, j }))) .then(({ ok, j }) => { if(!ok || !j.ok) throw new Error((j && j.error) || 'Fehlgeschlagen'); rrUebernehmen(j); if(okText) rrMelden(okText); rrEigeneRechteAuffrischen(); return j; }) .catch(err => { rrMelden(err.message || String(err)); rrZeichnen(); return null; }); } // rrEigeneRechteAuffrischen: betrifft eine Änderung die eigene Rolle, holt // /whoami die neuen Rechte, damit Seitenleiste und Knöpfe gleich stimmen. function rrEigeneRechteAuffrischen(){ fetch(baseUrl() + '/whoami').then(r => r.json()).then(j => { if(j && j.user && currentUser && j.user.id === currentUser.id){ currentUser = j.user; if(typeof renderUserBar === 'function') renderUserBar(); } }).catch(() => {}); } const rrRolle = key => rrDaten && rrDaten.rollen.find(r => r.key === key); const rrAlleRechte = () => rrDaten.katalog.flatMap(g => g.rechte.map(r => r.key)); // Aktionen (aus den Inline-Handlern) function rrWaehlen(key){ rrAuswahl = key; rrLoeschenBestaetigen = ''; rrZeichnen(); } function rrVergleichSetzen(key){ rrVergleich = key; rrZeichnen(); } function rrNeuUmschalten(offen){ rrNeuOffen = offen; rrZeichnen(); if(offen){ const i = document.getElementById('rrNeuName'); if(i) i.focus(); } } function rrRechtSchalten(key, recht){ const r = rrRolle(key); if(!r || r.fest) return; const an = !r.rechte.includes(recht); rrSenden('PUT', '/rollen/' + encodeURIComponent(key) + '/rechte', { rechte: [recht], an }, 'Gespeichert'); } function rrGruppeSchalten(key, gi){ const r = rrRolle(key); if(!r || r.fest) return; const keys = rrDaten.katalog[gi].rechte.map(x => x.key); const an = !keys.every(k => r.rechte.includes(k)); rrSenden('PUT', '/rollen/' + encodeURIComponent(key) + '/rechte', { rechte: keys, an }, 'Gespeichert'); } function rrAnlegen(){ const name = (document.getElementById('rrNeuName') || {}).value || ''; const von = (document.getElementById('rrNeuVon') || {}).value || ''; if(!name.trim()){ rrMelden('Bitte einen Namen eingeben.'); return; } rrSenden('POST', '/rollen', { name: name.trim(), vonRolle: von }, 'Rolle „' + name.trim() + '“ angelegt.') .then(j => { if(j && j.neu){ rrAuswahl = j.neu; rrNeuOffen = false; rrZeichnen(); } }); } function rrUmbenennen(key, name){ const r = rrRolle(key); if(!r || r.fest || !name.trim() || name.trim() === r.name) return; rrSenden('PATCH', '/rollen/' + encodeURIComponent(key), { name: name.trim() }, 'Umbenannt.'); } function rrKopieren(key){ const r = rrRolle(key); if(!r) return; let name = r.name + ' (Kopie)', i = 2; while(rrDaten.rollen.some(x => x.name.toLowerCase() === name.toLowerCase())) name = r.name + ' (Kopie ' + (i++) + ')'; rrSenden('POST', '/rollen', { name, vonRolle: key }, 'Kopie angelegt.') .then(j => { if(j && j.neu){ rrAuswahl = j.neu; rrZeichnen(); } }); } // rrLoeschen: zweistufig im Knopf selbst (kein confirm()) — erster Klick // fragt nach, zweiter löscht. let rrLoeschenBestaetigen = ''; function rrLoeschen(key){ if(rrLoeschenBestaetigen !== key){ rrLoeschenBestaetigen = key; rrZeichnen(); return; } rrLoeschenBestaetigen = ''; rrSenden('DELETE', '/rollen/' + encodeURIComponent(key), null, 'Rolle gelöscht.'); } // rrZeichnen: baut die ganze Maske. Fokus und Eingaben im Formular „Neue // Rolle“ bleiben erhalten. function rrZeichnen(){ const el = document.getElementById('rollenRechte'); if(!el || !rrDaten) return; const fokusId = document.activeElement && document.activeElement.id; const neuName = (document.getElementById('rrNeuName') || {}).value || ''; const alle = rrAlleRechte(); const r = rrRolle(rrAuswahl) || rrDaten.rollen[0]; const v = rrVergleich && rrVergleich !== r.key ? rrRolle(rrVergleich) : null; const rechteVon = x => x.fest ? alle : x.rechte; const liste = rrDaten.rollen.map(x => { const n = rechteVon(x).length; return ``; }).join(''); const neu = rrNeuOffen ? `
` : ``; const gruppen = rrDaten.katalog.map((g, gi) => { const rechteR = rechteVon(r); const n = g.rechte.filter(x => rechteR.includes(x.key)).length; const zeilen = g.rechte.map(x => { const an = rechteR.includes(x.key); const vAn = v ? rechteVon(v).includes(x.key) : null; return `
${esc(x.name)}${x.hinweis ? `${esc(x.hinweis)}` : ''} ${v ? `${vAn ? 'ja' : 'nein'}` : ''}
`; }).join(''); return `
${esc(g.gruppe)}${n} von ${g.rechte.length} ${r.fest ? '' : ``}
${zeilen}
`; }).join(''); const loeschText = rrLoeschenBestaetigen === r.key ? 'Wirklich löschen?' : 'Löschen'; el.innerHTML = `
${liste}${neu}
${r.fest ? 'Administrator hat immer alle Rechte.' : ` `} ${esc(rrMeldung)}
${gruppen}
`; const ni = document.getElementById('rrNeuName'); if(ni && neuName) ni.value = neuName; if(fokusId){ const f = document.getElementById(fokusId); if(f) f.focus(); } }