Workspace: Rolle "Management" heisst jetzt "DogFather"

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.
This commit is contained in:
2026-08-28 11:55:05 +02:00
parent 55450aa83f
commit 1c3e642f12
20 changed files with 54 additions and 36 deletions
+4 -4
View File
@@ -129,7 +129,7 @@ function nameAusKopf(req) {
function darfHochladen(req, res, next) { function darfHochladen(req, res, next) {
if (!DARF_HOCHLADEN.has(req.person.rolle)) { if (!DARF_HOCHLADEN.has(req.person.rolle)) {
return res.status(403).json({ return res.status(403).json({
fehler: "Dateien legen das Management und die Scouts ab.", fehler: "Dateien legen DogFather und die Scouts ab.",
}); });
} }
next(); next();
@@ -263,10 +263,10 @@ dateienRouter.patch("/workspace/api/dateien/:id", gleicheHerkunft, (req, res) =>
/* "freigegeben" ist eine Abnahme -- die trifft laut Konzept das /* "freigegeben" ist eine Abnahme -- die trifft laut Konzept das
Management, nicht der, der die Datei hochgeladen hat. */ Management, nicht der, der die Datei hochgeladen hat. */
if (req.body.status === "freigegeben" && req.person.rolle !== "admin") { if (req.body.status === "freigegeben" && req.person.rolle !== "admin") {
return res.status(403).json({ fehler: "Freigeben darf nur das Management." }); return res.status(403).json({ fehler: "Freigeben darf nur DogFather." });
} }
if (datei.status === "freigegeben" && req.person.rolle !== "admin") { if (datei.status === "freigegeben" && req.person.rolle !== "admin") {
return res.status(403).json({ fehler: "Freigegebene Dateien ändert nur das Management." }); return res.status(403).json({ fehler: "Freigegebene Dateien ändert nur DogFather." });
} }
setz.push("status = ?"); daten.push(req.body.status); setz.push("status = ?"); daten.push(req.body.status);
} }
@@ -309,7 +309,7 @@ dateienRouter.delete("/workspace/api/dateien/:id", gleicheHerkunft, (req, res) =
return res.status(403).json({ fehler: "nicht_erlaubt" }); return res.status(403).json({ fehler: "nicht_erlaubt" });
} }
if (datei.status === "freigegeben") { if (datei.status === "freigegeben") {
return res.status(403).json({ fehler: "Freigegebene Dateien löscht nur das Management." }); return res.status(403).json({ fehler: "Freigegebene Dateien löscht nur DogFather." });
} }
} }
+1 -1
View File
@@ -180,7 +180,7 @@ personenRouter.patch("/workspace/api/verwaltung/personen/:id", gleicheHerkunft,
if (!aktiv && person.rolle === "admin") { if (!aktiv && person.rolle === "admin") {
const { n } = db().prepare( const { n } = db().prepare(
"SELECT COUNT(*) AS n FROM personen WHERE rolle = 'admin' AND aktiv = 1").get(); "SELECT COUNT(*) AS n FROM personen WHERE rolle = 'admin' AND aktiv = 1").get();
if (n <= 1) return res.status(400).json({ fehler: "Das ist das letzte aktive Management." }); if (n <= 1) return res.status(400).json({ fehler: "Das ist der letzte aktive DogFather-Zugang." });
} }
personSperren(id, aktiv, { ...req.person, ip: echteIp(req) }); personSperren(id, aktiv, { ...req.person, ip: echteIp(req) });
} }
+1 -1
View File
@@ -227,7 +227,7 @@ scoutRouter.delete("/workspace/api/leads/:id", gleicheHerkunft, (req, res) => {
/* Ein übergebener Lead ist Teil der Betreuungsgeschichte -- den /* Ein übergebener Lead ist Teil der Betreuungsgeschichte -- den
räumt nur das Management weg. */ räumt nur das Management weg. */
if (req.person.rolle !== "admin" && lead.status === "uebergeben") { if (req.person.rolle !== "admin" && lead.status === "uebergeben") {
return res.status(403).json({ fehler: "Übergebene Leads löscht nur das Management." }); return res.status(403).json({ fehler: "Übergebene Leads löscht nur DogFather." });
} }
db().prepare("DELETE FROM leads WHERE id = ?").run(id); db().prepare("DELETE FROM leads WHERE id = ?").run(id);
protokolliere("lead_geloescht", { protokolliere("lead_geloescht", {
+19 -1
View File
@@ -54,6 +54,21 @@ const VERSUCHE_MAX = 8; // pro IP
const VERSUCHE_FENSTER_MIN = 10; const VERSUCHE_FENSTER_MIN = 10;
const ROLLEN = new Set(["admin", "creator", "scout"]); const ROLLEN = new Set(["admin", "creator", "scout"]);
/* Der Rollenschluessel bleibt "admin" -- er steckt in der CHECK-Regel der
Datenbank, in jeder Sitzung und in jeder Rechteabfrage. Umbenannt wird
nur, was man LIEST. Diese Zuordnung ist die einzige Stelle dafuer:
Stand der Anzeigename in elf Dateien, waere er beim naechsten Mal in
zehn davon geaendert.
Hinweis fuer spaeter: In den Kommentaren der Fachmodule heisst die
Rolle "admin" weiterhin "das Management". Gemeint ist dieselbe Rolle,
die in der Oberflaeche "DogFather" heisst. */
export const ROLLEN_NAME = {
admin: "DogFather",
creator: "Creator",
scout: "Scout",
};
/* scrypt-Parameter. N=2^15 braucht auf diesem Server rund 150 ms — spürbar /* scrypt-Parameter. N=2^15 braucht auf diesem Server rund 150 ms — spürbar
genug, um Rateversuche auszubremsen, und unauffällig für einen echten genug, um Rateversuche auszubremsen, und unauffällig für einen echten
Anmeldevorgang. Die Werte werden je Datensatz mitgespeichert, damit sie Anmeldevorgang. Die Werte werden je Datensatz mitgespeichert, damit sie
@@ -529,7 +544,10 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => {
erkennen, welcher Eintrag der eigene ist (etwa "das bin ich" in der erkennen, welcher Eintrag der eigene ist (etwa "das bin ich" in der
Personenliste), und das eigene Profil liesse sich gar nicht aufrufen. Personenliste), und das eigene Profil liesse sich gar nicht aufrufen.
Ein Geheimnis ist sie nicht -- sie beschreibt nur den Angemeldeten. */ Ein Geheimnis ist sie nicht -- sie beschreibt nur den Angemeldeten. */
res.json({ id: person.id, name: person.name, rolle: person.rolle }); res.json({
id: person.id, name: person.name, rolle: person.rolle,
rolle_name: ROLLEN_NAME[person.rolle] ?? person.rolle,
});
}); });
/* ---------- Verwaltung (nur über die Kommandozeile) -------------------- /* ---------- Verwaltung (nur über die Kommandozeile) --------------------
+1 -1
View File
@@ -260,7 +260,7 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe
ich = await antwort.json(); ich = await antwort.json();
} catch { return; } } catch { return; }
$('wer').textContent = `${ich.name} · ${ich.rolle}`; $('wer').textContent = `${ich.name} · ${ich.rolle_name || ich.rolle}`;
if (ich.rolle === 'admin') { if (ich.rolle === 'admin') {
$('feld-creator').hidden = false; $('feld-creator').hidden = false;
+1 -1
View File
@@ -200,7 +200,7 @@
if (!a.ok) { location.assign('/workspace/'); return; } if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
let daten; let daten;
try { try {
+1 -1
View File
@@ -328,7 +328,7 @@
if (!a.ok) { location.assign('/workspace/'); return; } if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
await laden(); await laden();
})(); })();
})(); })();
+1 -1
View File
@@ -214,7 +214,7 @@
if (!a.ok) { location.assign('/workspace/'); return; } if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
/* Creator laden nichts hoch (Wunsch 28.08.2026). Das Ablegefeld /* Creator laden nichts hoch (Wunsch 28.08.2026). Das Ablegefeld
verschwindet, stattdessen steht dort, warum -- ein einfach verschwindet, stattdessen steht dort, warum -- ein einfach
+1 -1
View File
@@ -15,7 +15,7 @@
const knopf = $('knopf'); const knopf = $('knopf');
const fuer = $('fuer'); const fuer = $('fuer');
const NAMEN = { admin: 'Management', creator: 'Creator', scout: 'Scout' }; const NAMEN = { admin: 'DogFather', creator: 'Creator', scout: 'Scout' };
const SPEICHER = 'dfw.rolle'; const SPEICHER = 'dfw.rolle';
let gewaehlt = null; let gewaehlt = null;
+1 -1
View File
@@ -242,7 +242,7 @@
if (!a.ok) { location.assign('/workspace/'); return; } if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
if (ich.rolle === 'admin') { if (ich.rolle === 'admin') {
$('feld-teilnehmer').hidden = false; $('feld-teilnehmer').hidden = false;
+4 -4
View File
@@ -1,12 +1,12 @@
/* =================================================================== /* ===================================================================
Personen und Zugangscodes verwalten. Nur für Management -- der Server Personen und Zugangscodes verwalten. Nur für DogFather -- der Server
liefert allen anderen ohnehin 404 auf diese Schnittstellen. liefert allen anderen ohnehin 404 auf diese Schnittstellen.
=================================================================== */ =================================================================== */
(() => { (() => {
'use strict'; 'use strict';
const $ = (id) => document.getElementById(id); const $ = (id) => document.getElementById(id);
const ROLLENNAME = { admin: 'Management', creator: 'Creator', scout: 'Scout' }; const ROLLENNAME = { admin: 'DogFather', creator: 'Creator', scout: 'Scout' };
let ich = null; let ich = null;
const el = (tag, klasse, text) => { const el = (tag, klasse, text) => {
@@ -88,7 +88,7 @@
const w = el('select', 'betreuung__wahl'); const w = el('select', 'betreuung__wahl');
w.id = 'betreuer-' + p.id; w.id = 'betreuer-' + p.id;
const leer = document.createElement('option'); const leer = document.createElement('option');
leer.value = ''; leer.textContent = 'nur Management'; leer.value = ''; leer.textContent = 'nur DogFather';
w.append(leer); w.append(leer);
for (const s2 of betreuerListe) { for (const s2 of betreuerListe) {
const o = document.createElement('option'); const o = document.createElement('option');
@@ -230,7 +230,7 @@
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
if (ich.rolle !== 'admin') { location.assign('/workspace/start.html'); return; } if (ich.rolle !== 'admin') { location.assign('/workspace/start.html'); return; }
$('wer').textContent = `${ich.name} · ${ich.rolle}`; $('wer').textContent = `${ich.name} · ${ich.rolle_name || ich.rolle}`;
await laden(); await laden();
})(); })();
})(); })();
+3 -3
View File
@@ -97,9 +97,9 @@
if (!a.ok) { location.assign('/workspace/'); return; } if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
/* Scouts betreuen ihre zugeteilten Creator wie das Management -- /* Scouts betreuen ihre zugeteilten Creator wie DogFather --
sie sehen aber nur die management-internen Felder nicht. */ sie sehen aber nur die management-internen Felder nicht. */
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
let liste; let liste;
try { liste = await (await hole('/workspace/api/profil')).json(); } catch { return; } try { liste = await (await hole('/workspace/api/profil')).json(); } catch { return; }
@@ -107,7 +107,7 @@
if (ich.rolle === 'admin' || ich.rolle === 'scout') { if (ich.rolle === 'admin' || ich.rolle === 'scout') {
if (!liste.creator.length) { if (!liste.creator.length) {
$('unterzeile').textContent = ich.rolle === 'scout' $('unterzeile').textContent = ich.rolle === 'scout'
? 'Dir ist noch kein Creator zugeteilt. Das Management legt fest, wen du betreust.' ? 'Dir ist noch kein Creator zugeteilt. DogFather legt fest, wen du betreust.'
: 'Es gibt noch keinen Creator. Lege zuerst unter Personen & Zugänge einen an.'; : 'Es gibt noch keinen Creator. Lege zuerst unter Personen & Zugänge einen an.';
return; return;
} }
+2 -2
View File
@@ -200,7 +200,7 @@
if (!a.ok) { location.assign('/workspace/'); return; } if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
try { try {
const { creator } = await (await hole('/workspace/api/report/creator')).json(); const { creator } = await (await hole('/workspace/api/report/creator')).json();
@@ -214,7 +214,7 @@
fuer ihn nicht, das waere die Gesamtuebersicht. */ fuer ihn nicht, das waere die Gesamtuebersicht. */
if (!creator.length) { if (!creator.length) {
$('unterzeile').textContent = $('unterzeile').textContent =
'Dir ist noch kein Creator zugeteilt. Das Management legt fest, wen du betreust.'; 'Dir ist noch kein Creator zugeteilt. DogFather legt fest, wen du betreust.';
return; return;
} }
$('creator-block').hidden = false; $('creator-block').hidden = false;
+2 -2
View File
@@ -340,7 +340,7 @@
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
if (ich.rolle === 'creator') { location.assign('/workspace/start.html'); return; } if (ich.rolle === 'creator') { location.assign('/workspace/start.html'); return; }
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
if (ich.rolle === 'admin') { if (ich.rolle === 'admin') {
$('unterzeile').textContent = 'Gesamtübersicht über alle Scouts.'; $('unterzeile').textContent = 'Gesamtübersicht über alle Scouts.';
@@ -358,7 +358,7 @@
} catch { return; } } catch { return; }
$('f-scout').addEventListener('change', zeichne); $('f-scout').addEventListener('change', zeichne);
} else { } else {
$('unterzeile').textContent = 'Deine Pipeline – nur du und das Management sehen sie.'; $('unterzeile').textContent = 'Deine Pipeline – nur du und DogFather sehen sie.';
} }
await laden(); await laden();
+2 -2
View File
@@ -7,7 +7,7 @@
'use strict'; 'use strict';
const ROLLENTEXT = { const ROLLENTEXT = {
admin: 'Management · Gesamtübersicht und Freigaben', admin: 'DogFather · Gesamtübersicht und Freigaben',
creator: 'Creator · dein eigener Bereich', creator: 'Creator · dein eigener Bereich',
scout: 'Scout · deine Pipeline und die Creator, die du betreust', scout: 'Scout · deine Pipeline und die Creator, die du betreust',
}; };
@@ -48,7 +48,7 @@
return; return;
} }
$('wer').textContent = `${ich.name} · ${ich.rolle}`; $('wer').textContent = `${ich.name} · ${ich.rolle_name || ich.rolle}`;
$('gruss').textContent = `Willkommen, ${ich.name}`; $('gruss').textContent = `Willkommen, ${ich.name}`;
$('rollentext').textContent = ROLLENTEXT[ich.rolle] || ich.rolle; $('rollentext').textContent = ROLLENTEXT[ich.rolle] || ich.rolle;
+2 -2
View File
@@ -254,7 +254,7 @@
if (!a.ok) { location.assign('/workspace/'); return; } if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json(); ich = await a.json();
} catch { return; } } catch { return; }
$('wer').textContent = ich.name + ' · ' + ich.rolle; $('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
let liste; let liste;
try { liste = await (await hole('/workspace/api/startcheck/creator')).json(); } catch { return; } try { liste = await (await hole('/workspace/api/startcheck/creator')).json(); } catch { return; }
@@ -262,7 +262,7 @@
if (!liste.creator.length) { if (!liste.creator.length) {
$('felder').textContent = ''; $('felder').textContent = '';
$('felder').append(el('p', 'leer-hinweis', ich.rolle === 'scout' $('felder').append(el('p', 'leer-hinweis', ich.rolle === 'scout'
? 'Dir ist noch kein Creator zugeteilt. Das Management legt fest, wen du betreust.' ? 'Dir ist noch kein Creator zugeteilt. DogFather legt fest, wen du betreust.'
: 'Es gibt noch keinen Creator.')); : 'Es gibt noch keinen Creator.'));
return; return;
} }
+1 -1
View File
@@ -44,7 +44,7 @@
Feld wirkt wie ein Fehler. --> Feld wirkt wie ein Fehler. -->
<p class="nur-lesen" id="nur-lesen" hidden> <p class="nur-lesen" id="nur-lesen" hidden>
Hier siehst du die Dateien deines Bereichs. <strong>Abgelegt werden sie Hier siehst du die Dateien deines Bereichs. <strong>Abgelegt werden sie
vom Management oder von Scouts</strong> – du kannst sie öffnen, aber nichts hochladen. von DogFather oder von Scouts</strong> – du kannst sie öffnen, aber nichts hochladen.
</p> </p>
<!-- Ablegefeld. Klick öffnet die Dateiauswahl, Ziehen funktioniert <!-- Ablegefeld. Klick öffnet die Dateiauswahl, Ziehen funktioniert
+4 -4
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Dogfather Universe · Creator Workspace · Anmeldung</title> <title>Dogfather Universe · Creator Workspace · Anmeldung</title>
<meta name="description" content="Privater Arbeitsbereich für Creator, Scouts und Management." /> <meta name="description" content="Privater Arbeitsbereich für Creator, Scouts und DogFather." />
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" /> <meta name="theme-color" content="#05070d" />
@@ -53,13 +53,13 @@
<header class="kopf"> <header class="kopf">
<p class="marke">Dogfather&nbsp;Universe</p> <p class="marke">Dogfather&nbsp;Universe</p>
<h1 class="titel">Creator&nbsp;Workspace</h1> <h1 class="titel">Creator&nbsp;Workspace</h1>
<p class="unterzeile">Privater Bereich für Creator, Scouts und Management.</p> <p class="unterzeile">Privater Bereich für Creator, Scouts und DogFather.</p>
</header> </header>
<div class="rollen" role="radiogroup" aria-labelledby="rollen-schild"> <div class="rollen" role="radiogroup" aria-labelledby="rollen-schild">
<p class="feldschild" id="rollen-schild">Womit meldest du dich an?</p> <p class="feldschild" id="rollen-schild">Womit meldest du dich an?</p>
<!-- Krone für das Management: steht für die Gesamtübersicht und die <!-- Krone für DogFather: steht für die Gesamtübersicht und die
Freigaben. Das Schild ist zum Scout gewandert -- dort passt es Freigaben. Das Schild ist zum Scout gewandert -- dort passt es
besser, weil ein Scout die Pipeline abschirmt und prüft, bevor besser, weil ein Scout die Pipeline abschirmt und prüft, bevor
etwas weitergereicht wird. --> etwas weitergereicht wird. -->
@@ -69,7 +69,7 @@
<path d="M6 20.1h12"/> <path d="M6 20.1h12"/>
</svg> </svg>
<span class="rolle__text"> <span class="rolle__text">
<span class="rolle__name">Management</span> <span class="rolle__name">DogFather</span>
<span class="rolle__zusatz">Gesamtübersicht &amp; Freigaben</span> <span class="rolle__zusatz">Gesamtübersicht &amp; Freigaben</span>
</span> </span>
</button> </button>
+1 -1
View File
@@ -50,7 +50,7 @@
<select id="f-rolle" name="rolle"> <select id="f-rolle" name="rolle">
<option value="creator" selected>Creator</option> <option value="creator" selected>Creator</option>
<option value="scout">Scout</option> <option value="scout">Scout</option>
<option value="admin">Management</option> <option value="admin">DogFather</option>
</select> </select>
</div> </div>
</div> </div>
+2 -2
View File
@@ -108,12 +108,12 @@
</div> </div>
</fieldset> </fieldset>
<!-- Nur fürs Management. Der Server schickt dieses Feld an Creator <!-- Nur für DogFather. Der Server schickt dieses Feld an Creator
gar nicht erst mit. --> gar nicht erst mit. -->
<fieldset class="gruppe gruppe--intern" id="gruppe-intern" hidden> <fieldset class="gruppe gruppe--intern" id="gruppe-intern" hidden>
<legend class="gruppe__titel">Interne Notiz</legend> <legend class="gruppe__titel">Interne Notiz</legend>
<p class="gruppe__text"> <p class="gruppe__text">
Sieht <strong>nur das Management</strong>. Der Creator bekommt dieses Feld Sieht <strong>nur DogFather</strong>. Der Creator bekommt dieses Feld
weder angezeigt noch übertragen. weder angezeigt noch übertragen.
</p> </p>
<textarea id="admin_notiz" rows="4" maxlength="2000"></textarea> <textarea id="admin_notiz" rows="4" maxlength="2000"></textarea>