diff --git a/server/pruef-haus-trennung.mjs b/server/pruef-haus-trennung.mjs index f0b0a4e6..c4e6cb93 100644 --- a/server/pruef-haus-trennung.mjs +++ b/server/pruef-haus-trennung.mjs @@ -330,7 +330,148 @@ console.log("=== Der Ring und die Hinweise ==="); } /* ======================================================================= - 9. Die Gegenprobe zur Regel selbst + 9. Personen & Zugaenge -- auf der Team-Seite nur das Team + ======================================================================= + + Filipe: "die kategorie personen & zugaenge fehlt also muss das + hinzugefuegt werden und bitte nur basiert auf diese seite." + + Sie fehlte, weil sie mit den Agenturkacheln verschwunden ist -- und + ausgerechnet sie ist die, mit der man jemandem eine Rolle gibt. + ======================================================================= */ +console.log(""); +console.log("=== Personen & Zugaenge ==="); +{ + const a = await beide("/workspace/api/ich", dogi.keks); + const namen = (x) => (x?.bereiche || []).map((k) => k.name); + ok(namen(a.crew).includes("Personen & Zugänge"), + `die Kachel steht auf crew. (${namen(a.crew).length} Kacheln)`); + + /* Und NICHT bei der rechten Hand: Die Seite haengt serverseitig an + nurAdmin, ein Knopf dorthin brachte ihr eine Absage. */ + const h = await hole("/workspace/api/ich", { host: CREW, keks: hand.keks }); + ok(!(h.daten?.bereiche || []).map((k) => k.name).includes("Personen & Zugänge"), + "und nicht bei der rechten Hand -- sie darf die Seite nicht"); + const hZugriff = await hole("/workspace/api/verwaltung/personen", { host: CREW, keks: hand.keks }); + ok(hZugriff.status === 404, + `die Seite selbst weist sie ab (${hZugriff.status}) -- die Kachel haette sie belogen`); + + const liste = await beide("/workspace/api/verwaltung/personen", dogi.keks); + const wer = (x) => (x?.personen || []).map((p) => p.name); + ok(wer(liste.crew).includes("Ayla") && wer(liste.crew).includes("VanVan"), + `auf crew. steht das Team (${wer(liste.crew).join(", ") || "leer"})`); + ok(!wer(liste.crew).some((n) => ["Luna", "Cigdem", "Patrick"].includes(n)), + "und niemand aus der Agentur"); + ok(["Luna", "Cigdem", "Patrick", "Ayla"].every((n) => wer(liste.ws).includes(n)), + `auf workspace. stehen alle (${wer(liste.ws).length})`); + + /* Die anlegbaren Rollen kommen aus derselben Auskunft, aus der die + Oberflaeche ihre Knoepfe baut. Auf crew. duerfen es nur Team-Rollen + sein -- sonst legt man dort versehentlich einen Creator an und + findet ihn erst auf der anderen Seite wieder. */ + const neuCrew = await hole("/workspace/api/verwaltung/personen", { host: CREW, + methode: "POST", keks: dogi.keks, + rumpf: JSON.stringify({ name: "Testcreator", rolle: "creator" }) }); + ok(neuCrew.status === 403, + `ein Creator laesst sich auf crew. nicht anlegen (${neuCrew.status})`); + /* GEGENPROBE: Auf der Agenturadresse geht genau das. Ohne sie hiesse + die 403 oben auch dann gruen, wenn Anlegen ueberhaupt kaputt waere. */ + const neuWs = await hole("/workspace/api/verwaltung/personen", { host: WS, + methode: "POST", keks: dogi.keks, + rumpf: JSON.stringify({ name: "Testcreator", rolle: "creator" }) }); + ok(neuWs.status === 201, `auf workspace. geht es (${neuWs.status})`); +} + +/* ======================================================================= + 10. Eine Rolle aendern -- es gab die Funktion gar nicht + ======================================================================= + + Beim Suchen nach "wie gebe ich VanVan die Rolle Rechte Hand" kam + heraus: Es gibt im ganzen Server keine Stelle, die `personen.rolle` + aendert. Anlegen ja, sperren ja, loeschen ja -- aendern nirgends. Wer + jemandem eine andere Aufgabe geben wollte, musste ihn loeschen und + neu anlegen, und daran haengen Aufgaben, Nachrichten und Eintraege. + ======================================================================= */ +console.log(""); +console.log("=== Die Rolle aendern ==="); +{ + const weg = "/workspace/api/verwaltung/personen"; + + /* Erst die Sicherungen, dann der Erfolgsfall -- sonst pruefe ich sie + auf einem Bestand, den der Erfolgsfall schon veraendert hat. */ + const selbst = await hole(`${weg}/${idDogi}/rolle`, { host: CREW, methode: "PUT", + keks: dogi.keks, rumpf: JSON.stringify({ rolle: "hand" }) }); + ok(selbst.status === 400, `die eigene Rolle laesst sich nicht aendern (${selbst.status})`); + + const erfunden = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT", + keks: dogi.keks, rumpf: JSON.stringify({ rolle: "gibtsnicht" }) }); + ok(erfunden.status === 400, `eine erfundene Rolle wird abgelehnt (${erfunden.status})`); + + const gleich = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT", + keks: dogi.keks, rumpf: JSON.stringify({ rolle: "modi" }) }); + ok(gleich.status === 400, `dieselbe Rolle noch einmal: abgelehnt (${gleich.status})`); + + /* Auf der Team-Adresse laesst sich niemand zum Creator machen -- die + Rollen kommen aus derselben Auskunft wie beim Anlegen. */ + const fremd = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT", + keks: dogi.keks, rumpf: JSON.stringify({ rolle: "creator" }) }); + ok(fremd.status === 403, `auf crew. auch nicht zum Creator (${fremd.status})`); + + /* DER LETZTE DOGFATHER. Es gibt genau einen -- VanVan ist in dieser + Pruefung schon die rechte Hand. Also einen zweiten anlegen, den + ersten darf man dann herabstufen, den zweiten nicht mehr. */ + const zweiter = await hole(weg, { host: WS, methode: "POST", keks: dogi.keks, + rumpf: JSON.stringify({ name: "Zweitzugang", rolle: "admin" }) }); + const idZweit = zweiter.daten?.id; + ok(zweiter.status === 201 && idZweit > 0, `ein zweiter DogFather-Zugang (${zweiter.status})`); + + const runter = await hole(`${weg}/${idZweit}/rolle`, { host: WS, methode: "PUT", + keks: dogi.keks, rumpf: JSON.stringify({ rolle: "scout" }) }); + ok(runter.status === 200, `solange es zwei gibt, darf einer wechseln (${runter.status})`); + + const letzter = await hole(`${weg}/${idDogi}/rolle`, { host: WS, methode: "PUT", + keks: dogi.keks, rumpf: JSON.stringify({ rolle: "scout" }) }); + ok(letzter.status === 400, `der letzte nicht mehr (${letzter.status})`); + + /* DER ERFOLGSFALL, und was daran haengt. */ + /* SIE MUSS ANGEMELDET SEIN, sonst prueft "die Sitzungen sind + beendet" gar nichts. Beim ersten Lauf stand hier `>= 0` und eine + Null -- ein gruener Haken ueber einem leeren Bestand. Genau die + Sorte Pruefung, die immer bestaetigt und deshalb nichts + bestaetigt. */ + await anmelden("modi", "CODE-TEAM-0001", CREW); + const sitzungenVorher = d.prepare( + "SELECT COUNT(*) AS n FROM sitzungen WHERE person_id = ?").get(idModi).n; + ok(sitzungenVorher > 0, `Ayla ist angemeldet (${sitzungenVorher} Sitzung(en))`); + + const um = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT", + keks: dogi.keks, rumpf: JSON.stringify({ rolle: "hand" }) }); + ok(um.status === 200, `aus dem Teammitglied wird die rechte Hand (${um.status})`); + const jetztRolle = d.prepare("SELECT rolle FROM personen WHERE id = ?").get(idModi)?.rolle; + ok(jetztRolle === "hand", `und es steht so in der Datenbank (${jetztRolle})`); + + /* SICHERUNG 4: Die Sitzungen sind weg. Eine Sitzung gehoert zu einer + ADRESSE, und welche das ist, entscheidet die Rolle. */ + const nachher = d.prepare( + "SELECT COUNT(*) AS n FROM sitzungen WHERE person_id = ?").get(idModi).n; + ok(nachher === 0, `ihre Sitzungen sind beendet (${nachher})`); + + /* SICHERUNG 5: Der Code bleibt. Er haengt am Menschen, nicht an der + Rolle -- ihn mitzutauschen hiesse, jemanden auszusperren, ohne es + zu merken. */ + const code = d.prepare("SELECT code_hash FROM personen WHERE id = ?").get(idModi)?.code_hash; + ok(!!code && code.length > 20, "der Zugangscode ist unveraendert"); + + /* Und es steht im Protokoll. Eine Rechteaenderung ohne Spur waere + genau die, nach der spaeter niemand mehr fragen kann. */ + const spur = d.prepare( + "SELECT detail FROM protokoll WHERE aktion = 'rolle_geaendert' ORDER BY id DESC LIMIT 1").get(); + ok(/modi -> hand/.test(spur?.detail || ""), + `das Protokoll nennt beide Rollen (${spur?.detail || "kein Eintrag"})`); +} + +/* ======================================================================= + 11. Die Gegenprobe zur Regel selbst ======================================================================= */ console.log(""); console.log("=== Haengt es wirklich an der Adresse? ==="); diff --git a/server/workspace-personen.js b/server/workspace-personen.js index 937b077f..2e5f88f1 100644 --- a/server/workspace-personen.js +++ b/server/workspace-personen.js @@ -15,7 +15,7 @@ import express from "express"; import { - db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE, istSpicy, verborgeneIds, TEAM_DOGI_ROLLEN, darfAnlegen, + db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE, istSpicy, verborgeneIds, TEAM_DOGI_ROLLEN, darfAnlegen, hausBedingung, } from "./workspace.js"; import { sicherungJetzt } from "./workspace-sicherung.js"; @@ -193,6 +193,7 @@ personenRouter.get("/workspace/api/verwaltung/personen", (req, res) => { FROM personen p LEFT JOIN betreuung b ON b.creator_id = p.id LEFT JOIN scout_zuteilung sz ON sz.scout_id = p.id + WHERE 1=1${hausBedingung(req.person, "p.rolle")} ORDER BY ${ROLLEN_SORTIERUNG.replace("rolle", "p.rolle")}, p.aktiv DESC, p.name`) .all().filter((z) => !weg.has(z.id)), /* Wer ueberhaupt als zustaendig eingetragen werden kann. @@ -691,6 +692,109 @@ personenRouter.post("/workspace/api/verwaltung/personen/:id/code", gleicheHerkun } }); +/* ===================================================================== + DIE ROLLE EINES MENSCHEN AENDERN (10.09.2026) + + Filipe wollte VanVan die Rolle "Rechte Hand" geben -- und konnte + nicht. Beim Nachsehen: Es gibt im ganzen Server KEINE einzige Stelle, + die `personen.rolle` aendert. Anlegen ja, sperren ja, loeschen ja; + die Rolle war ab dem ersten Tag in Stein. + + Das ist kein Schoenheitsfehler. Ohne diese Funktion bleibt nur, den + Menschen zu loeschen und neu anzulegen -- und daran haengen seine + Aufgaben, seine Nachrichten, seine Eintraege, sein ganzer Verlauf. + Kapitel 4 des Pflichtenhefts verlangt ausdruecklich das Gegenteil: + "Rolle/Kategorie zuweisen". + + --------------------------------------------------------------------- + FUENF SICHERUNGEN, UND JEDE HAT IHREN GRUND + + 1. NUR DOGFATHER. Wie beim Anlegen. Wer Rollen vergeben kann, kann + sich selbst zum DogFather machen -- deshalb haengt die ganze + Verwaltung an `nurAdmin`, und diese Route erst recht. + + 2. NIE DIE EIGENE. Wer sich selbst herabstuft, sperrt sich aus dem + Haus aus, und niemand kann es rueckgaengig machen -- die Funktion + dafuer haengt ja an der Rolle, die er gerade abgegeben hat. Das + ist keine Warnung wert, das ist eine Tuer, die zubleibt. + + 3. NIE DEN LETZTEN DOGFATHER. Dieselbe Ueberlegung eine Ebene weiter, + und dieselbe wie beim Sperren darunter: Ohne einen aktiven + DogFather gibt es niemanden mehr, der Zugaenge vergibt. + + 4. ALLE SITZUNGEN DIESER PERSON ENDEN. Eine Sitzung gehoert seit dem + 10.09.2026 zu einer ADRESSE, und welche das ist, entscheidet die + Rolle (sitzungPasstZurAdresse). Wer eben noch DogFather war und + jetzt rechte Hand ist, sitzt sonst mit einer Sitzung da, die auf + der Agenturadresse laeuft und dort nicht mehr hingehoert. Das + faellt beim naechsten Klick auf, nicht sofort -- und ein halb + gueltiger Zustand ist das Unangenehmste, was eine Rechteaenderung + hinterlassen kann. + + 5. DER CODE BLEIBT. Er haengt am Menschen, nicht an der Rolle. Ihn + hier mitzutauschen waere bequem und falsch: Dann muesste jede + Rollenaenderung von einem Gespraech begleitet sein ("dein Zugang + ist ein anderer"), und wer das vergisst, sperrt jemanden aus, + ohne es zu merken. Wer einen neuen Code will, hat den Knopf + daneben. + ===================================================================== */ +personenRouter.put("/workspace/api/verwaltung/personen/:id/rolle", gleicheHerkunft, + (req, res) => { + try { + const id = Number(req.params.id); + const rolle = String(req.body?.rolle ?? ""); + if (!Number.isInteger(id)) return res.status(400).json({ fehler: "ungueltig" }); + + const person = db().prepare("SELECT id, name, rolle, aktiv FROM personen WHERE id = ?").get(id); + if (!person) return res.status(404).json({ fehler: "nicht_gefunden" }); + + /* Dieselbe Auskunft wie beim Anlegen: Wer eine Rolle nicht + vergeben darf, darf sie auch nicht zuweisen. Sonst waere das + Zuweisen der bequemere Weg an der Regel vorbei. */ + if (!ROLLEN.includes(rolle)) return res.status(400).json({ fehler: "Unbekannte Rolle." }); + if (!darfAnlegen(req.person).includes(rolle)) { + return res.status(403).json({ fehler: "Diese Rolle vergibst du nicht." }); + } + if (person.rolle === rolle) { + return res.status(400).json({ fehler: "Diese Rolle hat sie schon." }); + } + + /* SICHERUNG 2 */ + if (id === req.person.id) { + return res.status(400).json({ fehler: "Die eigene Rolle lässt sich nicht ändern.", + hinweis: "Sonst könntest du dich selbst aussperren, ohne es rückgängig machen zu können." }); + } + + /* SICHERUNG 3 -- gezaehlt werden die AKTIVEN. Ein gesperrter + DogFather kann niemanden hereinlassen; ihn mitzuzaehlen waere + eine Sicherung, die sich selbst belügt. */ + if (person.rolle === "admin" && rolle !== "admin") { + const uebrig = db().prepare( + "SELECT COUNT(*) AS n FROM personen WHERE rolle = 'admin' AND aktiv = 1 AND id <> ?") + .get(id).n; + if (uebrig < 1) { + return res.status(400).json({ fehler: "Das ist der letzte DogFather-Zugang." }); + } + } + + const vorher = person.rolle; + db().prepare("UPDATE personen SET rolle = ? WHERE id = ?").run(rolle, id); + + /* SICHERUNG 4 */ + const raus = db().prepare("DELETE FROM sitzungen WHERE person_id = ?").run(id); + + protokolliere("rolle_geaendert", { + personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req), + detail: `#${id} ${person.name}: ${vorher} -> ${rolle}`.slice(0, 120), + }); + res.json({ id, name: person.name, vorher, rolle, + abgemeldet: Number(raus.changes || 0) }); + } catch (fehler) { + console.error("[workspace] Rolle aendern:", fehler?.message); + res.status(503).json({ fehler: "nicht_verfuegbar" }); + } + }); + /* ---------- Sperren / Entsperren ---------------------------------------- */ personenRouter.patch("/workspace/api/verwaltung/personen/:id", gleicheHerkunft, nurDogFatherBeiLeitung, (req, res) => { diff --git a/server/workspace.js b/server/workspace.js index 6a4ac51f..fc21b8bb 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -165,7 +165,20 @@ const ANLEGBAR = { * wer nichts darf, bekommt eine leere Liste und keinen Sonderfall. */ export function darfAnlegen(person) { if (!person) return []; - return [...(ANLEGBAR[person.rolle] ?? [])]; + const alle = [...(ANLEGBAR[person.rolle] ?? [])]; + /* AUF DER TEAM-ADRESSE NUR TEAM-ROLLEN (10.09.2026). + + Filipe: "bitte nur basiert auf diese seite." Wer dort jemanden + anlegt, meint jemanden aus dem Team -- einen Creator dort + einzutragen waere ein Versehen, das man erst auf der anderen Seite + bemerkt. + + DIESELBE AUSKUNFT BAUT AUCH DIE KNOEPFE. `darfAnlegen` ist die + Quelle sowohl fuer die Pruefung in der Route als auch fuer die + Auswahl in der Oberflaeche -- deshalb verschwinden die anderen + Rollen dort von selbst, statt eine Absage zu bringen. */ + if (person.haus !== "crew") return alle; + return alle.filter((r) => HAUS_TEAM_ROLLEN.has(r)); } /* WER FUEHRT TEAM DOGI? (10.09.2026) @@ -519,6 +532,31 @@ const EINGANG_KACHEL = { }; +/* PERSONEN & ZUGAENGE, ABER FUER DAS TEAM (10.09.2026) + + Filipe: "die kategorie personen & zugaenge fehlt also muss das + hinzugefuegt werden und bitte nur basiert auf diese seite." + + Sie fehlte, weil ich mit den Agenturkacheln auch diese entfernt habe + -- und ausgerechnet sie ist die, mit der man jemandem eine Rolle gibt. + Ohne sie war die Team-Adresse eine Seite, auf der man das Team nicht + verwalten kann. + + NAME, ZEICHEN UND TON SIND DIESELBEN wie auf der Agenturseite. Es ist + dieselbe Seite mit demselben Zweck; ein zweiter Name dafuer waere ein + zweites Ding, das es nicht gibt. + + "NUR BASIERT AUF DIESE SEITE" steht nicht hier, sondern im Server: + Auf dieser Adresse liefert die Liste nur Team Dogi, und angelegt + werden koennen nur Team-Rollen (siehe hausBedingung und + darfAnlegen). Eine Kachel, die etwas anderes verspricht als die + Antwort dahinter, waere schlimmer als keine. */ +const PERSONEN_KACHEL = { + gruppe: "Für dich", gruppeUnter: "Dein Platz im Team", + name: "Personen & Zugänge", unter: "Wer dabei ist – und mit welchem Zugang", + zeichen: "personen", ton: 11, ziel: "personen.html", szene: "zentrale", +}; + /* DIE RECHTE HAND SIEHT DIESELBEN KACHELN WIE EIN MODI -- PLUS EINE. Blueprint V3.0, Kapitel 3.1: "Gleicher Ueberblick wie Owner. Kann @@ -553,7 +591,17 @@ export function bereicheFuer(person) { ihn waere die, die beim naechsten Umbau auseinanderlaeuft -- und sie stuende ausserdem gegen die Hausregel, ihn nicht ueber sein Team zu stellen. */ - if (person?.haus === "crew") return HAND_BEREICHE; + /* AUF DER TEAM-ADRESSE: die Kacheln des Teams, plus die Verwaltung. + + WARUM DIE VERWALTUNG NUR HIER UND NICHT IN HAND_BEREICHE: Die + Personenseite haengt serverseitig an `nurAdmin`. Stuende die Kachel + in der Liste der rechten Hand, fuehrte sie bei ihr auf eine 404 -- + ein Knopf, der eine Absage bringt, ist schlimmer als kein Knopf. + Wenn sie das duerfen soll, ist das eine eigene Entscheidung und + gehoert an dieselbe Stelle wie nurAdmin, nicht hierher. */ + if (person?.haus === "crew") { + return istDogFather(person) ? [...HAND_BEREICHE, PERSONEN_KACHEL] : HAND_BEREICHE; + } return null; } diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 47bb5c71..423cec16 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609102244", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609102339", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609102244", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609102339", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609102244", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609102339", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609102244", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609102339", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/js/personen.js b/workspace/assets/js/personen.js index 1f534de3..1341718c 100644 --- a/workspace/assets/js/personen.js +++ b/workspace/assets/js/personen.js @@ -229,6 +229,75 @@ $('code-kopieren').focus(); } + /* ======================================================================= + ROLLE ÄNDERN (10.09.2026) + + DIE AUSWAHL WIRD NICHT NOCH EINMAL GEBAUT, sondern aus dem + Anlege-Formular gelesen. Dort stehen genau die Rollen, die der + Server dieser Person zugesteht -- einschliesslich derer, die in + keiner ausgelieferten Datei stehen duerfen und erst nachträglich + dazukommen (siehe rollenwahlErgaenzen). + + Eine zweite Liste hier wäre die, in der eine Rolle fehlt oder eine + zu viel steht -- und beides fiele erst auf, wenn jemand sie + braucht. */ + let rolleFuer = null; + + function rolleFragen(p) { + rolleFuer = p; + $('rolle-name').textContent = `${p.name} · ${ROLLENNAME[p.rolle] || p.rolle}`; + const ziel = $('rolle-wahl'); + ziel.textContent = ''; + + const vorhanden = [...document.querySelectorAll('#f-rolle .rollenwahl__knopf')] + .map((b) => ({ wert: b.dataset.rolle, name: b.querySelector('.rollenwahl__name')?.textContent || b.dataset.rolle })) + .filter((r) => r.wert && r.wert !== p.rolle); + + if (!vorhanden.length) { + ziel.append(el('p', 'chat__leer', 'Es gibt keine andere Rolle, die du vergeben kannst.')); + } + for (const r of vorhanden) { + const b = el('button', 'rollenwahl__knopf'); + b.type = 'button'; + b.dataset.rolle = r.wert; + b.append(el('span', 'rollenwahl__name', r.name)); + b.addEventListener('click', () => { + for (const x of ziel.querySelectorAll('.rollenwahl__knopf')) delete x.dataset.an; + b.dataset.an = 'ja'; + }); + ziel.append(b); + } + $('rolle-kasten').hidden = false; + $('rolle-kasten').scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + + $('rolle-abbrechen').addEventListener('click', () => { + $('rolle-kasten').hidden = true; + rolleFuer = null; + }); + + $('rolle-los').addEventListener('click', async () => { + if (!rolleFuer) return; + const gewaehlt = $('rolle-wahl').querySelector('.rollenwahl__knopf[data-an="ja"]')?.dataset.rolle; + if (!gewaehlt) { melde('Wähle zuerst eine Rolle.'); return; } + /* Die Rückfrage nennt beide Rollen im Klartext. "Rolle ändern?" + allein sagt nicht, WAS sich ändert -- und es ist der Handgriff, + der jemandem den Zugang zu einer ganzen Seite nimmt oder gibt. */ + const name = $('rolle-wahl').querySelector('.rollenwahl__knopf[data-an="ja"] .rollenwahl__name')?.textContent || gewaehlt; + if (!confirm(`${rolleFuer.name} wird von "${ROLLENNAME[rolleFuer.rolle] || rolleFuer.rolle}" zu "${name}".\n\n` + + 'Der Zugangscode bleibt derselbe. Offene Sitzungen werden beendet.')) return; + + const a = await hole(`/workspace/api/verwaltung/personen/${rolleFuer.id}/rolle`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rolle: gewaehlt }), + }); + if (!a.ok) { melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.'); return; } + $('rolle-kasten').hidden = true; + rolleFuer = null; + await laden(); + }); + $('code-schliessen').addEventListener('click', () => { $('code-kasten').hidden = true; codeOffen = false; @@ -456,7 +525,13 @@ Weg zuerst. Und nur für DogFather: "Nur DogFather hat alle endgültigen Rechte" heißt genau hier etwas. Ein Manager kann weiterhin sperren, das reicht für den Alltag und ist umkehrbar. */ + /* ROLLE ÄNDERN steht VOR dem Löschen -- der umkehrbare Weg + zuerst. Bis heute war Löschen und Neuanlegen der einzige Weg, + jemandem eine andere Aufgabe zu geben; genau das soll dieser + Knopf ersparen. */ if (ich.rolle === 'admin') { + knoepfe.append(tuKnopf('Rolle ändern', () => rolleFragen(p))); + const weg = tuKnopf('Löschen', () => loeschenFragen(p)); weg.dataset.gefahr = 'ja'; knoepfe.append(weg); diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 5bbd8dc5..3073ed22 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - +
@@ -252,10 +252,10 @@ - - - - - + + + + +