From aadee98bd3fe3655e90a7d8d134ca065f245eaf1 Mon Sep 17 00:00:00 2001 From: Schalli Date: Fri, 9 Oct 2026 17:44:04 +0200 Subject: [PATCH] docs: alle Anleitungen gegen den Code geprueft und nachgearbeitet Anwender-, Administrations-, Betriebs- und Entwicklungsanleitung gegen Code und Oberflaechentexte abgeglichen; falsche und veraltete Stellen korrigiert, fehlende Funktionen ergaenzt. Willkommensmail: Hinweis nennt jetzt sechs statt vier Felder. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + CLAUDE.md | 2 +- apps/web/src/messages/de.json | 2 +- apps/web/src/messages/en.json | 2 +- docs/anleitung-administration.md | 46 +++++-- docs/anleitung-anwender.md | 37 +++-- docs/anleitung-betrieb.md | 45 +++++-- docs/anleitung-entwicklung.md | 224 +++++++++++++++++++++++++------ 8 files changed, 284 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217c118..00def49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T ### Behoben +- Willkommensmail: Der Hinweis über den Platzhaltern sprach von „vier Feldern“; die Platzhalter funktionieren in allen sechs Feldern, und so steht es jetzt auch da. - Sicherheit: Der Schutz davor, dass Tessera interne Adressen abruft (Favoriten-Symbole, Logos in Nextcloud-Status und das neue „Fehlendes Zertifikat holen“), erkennt jetzt auch versteckte Schreibweisen interner IPv6-Adressen, zum Beispiel die gemappte Form ::ffff:7f00:1, NAT64 und 6to4. Beim neuen Abruf prüft Tessera die Adresse außerdem noch einmal im Moment des Verbindens. Der Zertifikatsmanager ist zudem gegen präparierte Dateien abgesichert, die ihn verlangsamen oder zum Absturz bringen könnten: ZIP-Dateien, die sich zu riesigen Datenmengen entpacken (sogenannte ZIP-Bomben), Dateien aus tausenden unvollständigen Zertifikatsblöcken und Passwortschutz mit übermäßig vielen Rechenschritten werden abgewiesen oder übersprungen, und Uploads über der Gesamtgrenze bricht Tessera schon beim Empfang ab. Auch IPv6-Adressen in eckigen Klammern (zum Beispiel für öffentliche Server) werden dabei jetzt richtig beurteilt. - Zertifikatsmanager: Beim Zusammenführen ersetzte eine zweite Datei die erste; jetzt bleiben alle Dateien in der Liste. Zertifikate und Schlüssel mit elliptischen Kurven (EC) wurden bisher nicht erkannt; jetzt funktionieren sie in allen Reitern. - Desktop-App: Dateien lassen sich jetzt auch in der Desktop-App per Ziehen und Ablegen hochladen, zum Beispiel im Modul Dateien oder im Zertifikatsmanager. Bisher übernahm die App das Ablegen selbst, und auf der Seite kam nichts an (Linux und Windows). Dafür ist die neue Version der Desktop-App nötig. diff --git a/CLAUDE.md b/CLAUDE.md index ccf07a1..0360e23 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,7 @@ No identity provider is deployed. The installed system is a self-built login sta | Technology | Installed Version | Purpose | |------------|--------------------|---------| -| Tauri | 2.11.1 (CLI 2.11.3) | Desktop app wrapper. `apps/desktop` is scaffolding only as of `docs/anleitung-entwicklung.md` | +| Tauri | 2.11.1 (CLI 2.11.3) | Desktop app wrapper (Windows NSIS + Linux AppImage, built in CI, with updater); see `docs/anleitung-entwicklung.md` | ### Infrastructure & DevOps diff --git a/apps/web/src/messages/de.json b/apps/web/src/messages/de.json index 912b9b8..bb9cebf 100644 --- a/apps/web/src/messages/de.json +++ b/apps/web/src/messages/de.json @@ -806,7 +806,7 @@ }, "placeholders": { "title": "Platzhalter", - "intro": "Platzhalter setzt Tessera beim Versand durch die Angaben des jeweiligen Benutzers. Sie funktionieren in allen vier Feldern. Ein Klick auf einen Platzhalter fügt ihn an der Cursorposition des zuletzt benutzten Feldes ein.", + "intro": "Platzhalter setzt Tessera beim Versand durch die Angaben des jeweiligen Benutzers. Sie funktionieren in allen sechs Feldern. Ein Klick auf einen Platzhalter fügt ihn an der Cursorposition des zuletzt benutzten Feldes ein.", "colPlaceholder": "Platzhalter", "colMeaning": "Bedeutung", "colExample": "Beispiel", diff --git a/apps/web/src/messages/en.json b/apps/web/src/messages/en.json index 4452d93..cce62dd 100644 --- a/apps/web/src/messages/en.json +++ b/apps/web/src/messages/en.json @@ -806,7 +806,7 @@ }, "placeholders": { "title": "Placeholders", - "intro": "Tessera replaces placeholders with the details of each user when sending. They work in all four fields. Clicking a placeholder inserts it at the cursor position of the field you used last.", + "intro": "Tessera replaces placeholders with the details of each user when sending. They work in all six fields. Clicking a placeholder inserts it at the cursor position of the field you used last.", "colPlaceholder": "Placeholder", "colMeaning": "Meaning", "colExample": "Example", diff --git a/docs/anleitung-administration.md b/docs/anleitung-administration.md index 02dc556..2accaa8 100644 --- a/docs/anleitung-administration.md +++ b/docs/anleitung-administration.md @@ -84,7 +84,7 @@ Alles, was für die Anmeldung technisch nötig ist, fügt Tessera immer automati | `{{benutzername}}` | Benutzername für die Anmeldung | max.mustermann | | `{{email}}` | E-Mail-Adresse des Benutzers | max.mustermann@example.com | | `{{adresse}}` | Adresse von Tessera | https://tessera.example.com | -| `{{firma}}` | Name des Mandanten | Beispiel GmbH | +| `{{firma}}` | Name Ihrer Organisation | Beispiel GmbH | Andere Platzhalter kennt Tessera nicht: die Seite nennt sie schon beim Tippen („Unbekannter Platzhalter {{xyz}}“), und das Speichern wird abgelehnt. @@ -188,14 +188,18 @@ Sucht gezielt einzelne AD-Benutzer über einen Freitext (Name, Benutzername oder Eine Liste einzelner Benutzernamen, die **nie** importiert werden – typischerweise technische Dienstkonten wie `administrator`, `krbtgt`, `guest` oder `ldap$`. Sie wirkt zusätzlich zum Gruppen-/OU-Filter, also unabhängig davon, ob der Eintrag im gefilterten Suchbereich läge. Die Liste muss über „Ausschlussliste speichern“ gesichert werden. +### Zeitplan + +Der Abschnitt **Zeitplan** erscheint, sobald die Verbindung einmal gespeichert wurde. Er legt fest, wie oft Tessera das Verzeichnis selbstständig abgleicht und ob die Anbindung eingeschaltet ist. Er hat einen eigenen Knopf „Speichern“. Er enthält zwei Einstellungen: + +- **Sync-Intervall** (in Minuten) – der Wert **0** bedeutet: automatische Synchronisation ist deaktiviert, der Abgleich läuft dann nur von Hand (die Seite zeigt dazu „deaktiviert“ neben dem Feld). Ein Wert größer 0 lässt Tessera in diesem Abstand automatisch synchronisieren. Beim allerersten Speichern der Verbindung ist das Intervall mit **60 Minuten** vorbelegt; Sie können es im Zeitplan jederzeit ändern. +- **Schalter „Aktiviert“ / „Deaktiviert“** – schaltet die Anbindung insgesamt ein oder aus (siehe den Hinweis unten). + +**Wichtiges, im Code verifiziertes Verhalten:** Der Schalter „Aktiviert/Deaktiviert“ im Abschnitt „Zeitplan“ (Feld `isActive` der LDAP-Konfiguration) ist **kein** „Auto-Sync aus“-Schalter. Er entscheidet serverseitig, ob sich Benutzer, deren Konto kein lokales Passwort hat (also alle per LDAP importierten Konten), überhaupt noch **anmelden** dürfen. Wird dieser Schalter deaktiviert, werden nicht etwa nur zukünftige Sync-Läufe pausiert – jeder verzeichnisgeführte Benutzer wird sofort von der Anmeldung ausgesperrt, unabhängig vom Sync-Intervall. Um ausschließlich die automatische Synchronisation abzuschalten, ohne bestehende Anmeldungen zu blockieren, ist stattdessen das Sync-Intervall auf 0 zu setzen. + ### Synchronisation -Zwei Zeitpunkte für den Sync-Lauf: - -- **Sync-Intervall (Minuten)** – der Wert **0** bedeutet: automatische Synchronisation ist deaktiviert. Ein Wert größer 0 lässt Tessera in diesem Abstand automatisch synchronisieren. -- **„Jetzt synchronisieren“** – löst unabhängig vom Intervall sofort einen Sync-Lauf aus. - -**Wichtiges, im Code verifiziertes Verhalten:** Der Schalter „Aktiviert/Deaktiviert“ in diesem Abschnitt (Feld `isActive` der LDAP-Konfiguration) ist **kein** „Auto-Sync aus“-Schalter. Er entscheidet serverseitig, ob sich Benutzer, deren Konto kein lokales Passwort hat (also alle per LDAP importierten Konten), überhaupt noch **anmelden** dürfen. Wird dieser Schalter deaktiviert, werden nicht etwa nur zukünftige Sync-Läufe pausiert – jeder verzeichnisgeführte Benutzer wird sofort von der Anmeldung ausgesperrt, unabhängig vom Sync-Intervall. Um ausschließlich die automatische Synchronisation abzuschalten, ohne bestehende Anmeldungen zu blockieren, ist stattdessen das Sync-Intervall auf 0 zu setzen. +Der Abschnitt **Synchronisation** zeigt den Zeitpunkt der letzten Synchronisation („Noch nie synchronisiert“, solange noch keiner lief). Über **„Jetzt synchronisieren“** lösen Sie unabhängig vom Zeitplan sofort einen Sync-Lauf aus. Nach einem Lauf zeigt Tessera einen Ergebnisbericht mit mehreren Zeilen: @@ -230,6 +234,24 @@ Beim Umschalten eines deaktivierten Moduls auf „Aktiviert“ öffnet sich ein Ein bereits aktiviertes Modul lässt sich direkt über den Schalter wieder deaktivieren, ohne Rückfrage. +### Die Modulliste + +Die Seite **Administration → Module** listet alle verfügbaren Module, nach Kategorie gruppiert. Zu jedem Modul sehen Sie: + +- den Namen, daneben die **Version** des Moduls (mit vorangestelltem „v“); +- die Markierung **System**, wenn es sich um ein mitgeliefertes Modul von Tessera handelt (das trifft derzeit auf alle Module zu; auch sie müssen für Ihre Organisation erst aktiviert werden); +- eine kurze Beschreibung; +- den Zustand „Aktiviert“ oder „Deaktiviert“ und den Schalter zum Umschalten. + +Oben auf der Seite führen die Verweise **„Freigaben-Matrix“** und **„Kategorien“** direkt zu den beiden Seiten, auf denen Sie die Freigaben und die Gruppierung der Module festlegen (siehe die folgenden Abschnitte). + +#### Zertifikatsmanager und Domaincheck + +Diese beiden Module (Kategorien „Sicherheit“ bzw. „Domains“) haben keine eigenen Einstellungen, die Sie als Administrator pflegen müssten. Es genügt, sie zu aktivieren (Schalter in der Modulliste) und die Freigabe „Benutzen“ an die gewünschten Gruppen oder Benutzer zu vergeben; Administratoren haben nach der Aktivierung automatisch Zugriff. Die Stufe „Verwalten“ schaltet bei beiden nichts zusätzlich frei. + +- **Zertifikatsmanager:** Anwender analysieren, konvertieren und bearbeiten Zertifikate. Der Zertifikatsmanager speichert nichts – weder Zertifikate noch Schlüssel oder Passwörter. Eine Ausnahme im Ablauf ist das Nachladen eines fehlenden Zwischenzertifikats („Fehlendes Zertifikat holen“): Das geschieht nur auf Knopfdruck des Anwenders, und der Tessera-Server ruft dann die im Zertifikat hinterlegte Adresse ab, sofern sie öffentlich erreichbar ist. Interne Adressen lehnt er ab. +- **Domaincheck:** Anwender prüfen, ob eine Domain frei oder bereits registriert ist; das Modul schlägt dazu auch die Endungen .de, .com, .net und .org vor. Es fragt dafür den Namensdienst (DNS) ab, hat keine Einstellungen und braucht keine Zugangsdaten. Wer Domains tatsächlich registrieren soll, benötigt das getrennte Modul „Domains“ (siehe „Domains: AutoDNS anbinden“). + ### Freigaben-Matrix Unter „Administration → Freigaben“ erscheint eine Tabelle: Zeilen sind die aktivierten Module (nach Kategorie gruppiert), Spalten sind die Gruppen des Mandanten (angezeigt mit ihrem internen Namen, falls gesetzt, sonst mit dem regulären Namen). Jede Zelle ist eine Checkbox, die die Freigabe für genau diese Kombination aus Modul und Gruppe an- oder abschaltet – Änderungen werden sofort gespeichert. Bei einer freigegebenen Zelle erscheint neben der Checkbox ein Auswahlfeld für die **Stufe**: „Benutzen“ (Standard) oder „Verwalten“. Ein erneutes Ankreuzen ändert die Stufe nie; sie wechseln Sie nur über das Auswahlfeld. Über das Suchfeld lässt sich sowohl nach Modul- als auch nach Gruppennamen filtern; ein Treffer auf nur einer Achse leert die andere Achse nicht. @@ -251,6 +273,8 @@ Was „Verwalten“ je Modul freischaltet: | Handelsware | Reiter „Einstellungen“ (Standard-Erlöskonto, Startwert Gegenkonto) | | Proxmox | Server anlegen, ändern und löschen, Verbindung prüfen, „Jetzt aktualisieren“ | | Nextcloud-Status | Clouds eintragen, ändern und entfernen, Logos hinterlegen, „Jetzt prüfen“ | +| Domains | Anbindung einrichten, Kunden und Kontakte anlegen, Domains registrieren (siehe „Domains: AutoDNS anbinden“) | +| Dateien | Reiter „Einstellungen“ ansehen und „Verbindung prüfen“; die Adresse der Nextcloud ändern nur Administratoren (siehe „Dateien: Nextcloud anbinden“) | | DKV-Rechnung | das gesamte Modul – „Benutzen“ allein reicht dort nicht; Benutzer mit nur „Benutzen“ sehen eine Seite, die das erklärt | Hat jemand über mehrere Wege Zugriff (zum Beispiel direkt „Benutzen“ und über eine Gruppe „Verwalten“), gilt die **höhere** Stufe. @@ -419,6 +443,12 @@ Das Modul „Domains“ (Gruppe Domains) aktivieren Sie wie jedes Modul im Markt Unter **Administration → E-Mail-Versand (SMTP)** wird der Mailversand konfiguriert: Host, Port, Verschlüsselung (Keine, STARTTLS oder SSL-TLS), Benutzername, Passwort, die Absenderadresse und optional das Feld „Fehlermeldungen an“ (siehe unten). Das Passwortfeld wird aus Sicherheitsgründen nie mit dem gespeicherten Wert vorbefüllt – es bleibt beim Laden immer leer und wird nur mitgesendet, wenn tatsächlich ein neuer Wert eingegeben wurde. +Die Seite ist in drei Abschnitte gegliedert: + +- **Server** – Host, Port, Verschlüsselung, Benutzername und Passwort des Mailservers. Neben dem Passwortfeld blendet der Knopf **„Passwort anzeigen“** (danach „Passwort verbergen“) die Eingabe zur Kontrolle im Klartext ein. +- **Absender und Empfänger** – die Absenderadresse („Absenderadresse (Von)“) und das Feld „Fehlermeldungen an“. +- **Prüfen und speichern** – die Knöpfe „Verbindung testen“ und „Einstellungen speichern“ sowie das Feld „Test-E-Mail an“. Der Test verwendet die Werte, die gerade im Formular stehen, Sie können ihn also vor dem Speichern ausführen. + Über „Test-E-Mail an“ lässt sich optional eine echte Testnachricht an eine beliebige Adresse verschicken, um die Konfiguration vor dem produktiven Einsatz zu prüfen. **Fehlermeldungen an** ist eine optionale Adresse für den Knopf „Fehler melden“, den alle Anwender rechts in der Kopfleiste sehen. Sobald hier eine Adresse gespeichert ist, wirkt der Knopf: Ein Klick schickt ein Bildschirmfoto der aktuellen Seite samt Beschreibung des Anwenders, Adresse der Seite, Version und Kanal von Tessera, Browser, angemeldetem Benutzer (Name, Benutzername, Rolle) und den letzten Fehlermeldungen des Browsers als E-Mail an diese Adresse — der Betreff beginnt mit „[Tessera Fehlermeldung]“ und einem Kürzel für die Herkunft der Meldung („[Browser]“, „[Desktop/Windows]“ oder „[Desktop/Linux]“), nach dem sich das Postfach sortieren oder filtern lässt; im Text nennt die Zeile „Herkunft“ bei Browsern Browser und Betriebssystem (Beispiel „Browser — Chrome 129 auf Windows“), bei der Desktop-App Betriebssystem, Version und Stand (Beispiel „Desktop-App (Windows), Tessera-App 1.2.0 · Stand a6d1a64“); das Bild hängt als PNG an. Der Knopf ist immer sichtbar; ohne Adresse erhalten Anwender beim Senden den Hinweis, dass noch kein Postfach eingerichtet ist (Administratoren sehen zusätzlich einen Link hierher). Je Benutzer sind höchstens fünf Meldungen in zehn Minuten möglich; Bilder über 4 MB werden abgewiesen. Der Versand nutzt dieselben SMTP-Zugangsdaten wie alle anderen E-Mails des Mandanten. Für Installationen ohne gespeicherte SMTP-Einstellungen kennt der Betrieb einen Rückfall über die Umgebungsvariable `TESSERA_BUGREPORT_TO` (Betriebshandbuch, Kapitel 3). Bitte beachten: Das Bild zeigt alles, was der Anwender gerade sieht — wählen Sie das Postfach entsprechend. @@ -446,7 +476,7 @@ Ein Mandant lässt sich **nicht löschen, solange er noch aktive Benutzer hat** | Symptom | Wahrscheinliche Ursache | |---|---| -| Alle Benutzer aus dem Active Directory können sich plötzlich nicht mehr anmelden, obwohl der Sync-Lauf zuvor erfolgreich war. | Der Schalter „Aktiviert/Deaktiviert“ im Sync-Bereich der LDAP-Konfiguration wurde ausgeschaltet. Dieser Schalter sperrt sofort jede Anmeldung verzeichnisgeführter Konten – er ist **nicht** gleichbedeutend mit einer pausierten Synchronisation (siehe Kapitel 4). Zum Aussetzen nur der automatischen Synchronisation stattdessen das Sync-Intervall auf 0 setzen und den Schalter aktiviert lassen. | +| Alle Benutzer aus dem Active Directory können sich plötzlich nicht mehr anmelden, obwohl der Sync-Lauf zuvor erfolgreich war. | Der Schalter „Aktiviert/Deaktiviert“ im Abschnitt „Zeitplan“ der LDAP-Konfiguration wurde ausgeschaltet. Dieser Schalter sperrt sofort jede Anmeldung verzeichnisgeführter Konten – er ist **nicht** gleichbedeutend mit einer pausierten Synchronisation (siehe Kapitel 4). Zum Aussetzen nur der automatischen Synchronisation stattdessen das Sync-Intervall auf 0 setzen und den Schalter aktiviert lassen. | | „Verbindung testen“ oder ein Sync-Lauf schlägt mit einem Bind-/Authentifizierungsfehler fehl (z. B. einer Meldung, die auf einen fehlgeschlagenen Sicherheitskontext hinweist). | Häufigste Ursache ist eine vertauschte Eingabe von Bind-DN und Basis-DN, oder ein falsches Bind-Passwort. Beide Felder in den Verbindungseinstellungen prüfen. | | Ein AD-Benutzer wird zwar synchronisiert, aber ohne E-Mail-Adresse angelegt oder aktualisiert; Passwort-Reset-Mails erreichen ihn nicht. | Normales, dokumentiertes Verhalten seit E-Mail optional ist: Die im AD hinterlegte Adresse gehört bereits zu einem anderen Konto und wird deshalb nicht übernommen. Der Ergebnisbericht des Sync-Laufs listet solche Fälle unter „Konten ohne E-Mail-Adresse“ auf. Die Kollision im betroffenen Konto (dem Halter der Adresse) prüfen und die Adresse dort ggf. korrigieren. | | Eine AD-Gruppe erscheint im Sync-Bericht als gelöscht, obwohl sie im Verzeichnis noch existiert. | Die Gruppe wurde vermutlich außerhalb der konfigurierten Basis-DN(s) verschoben. Tessera prüft vor einer Löschung zwar zusätzlich die Domänenwurzel, kann eine Verschiebung aber nur innerhalb der erreichbaren Suchbereiche erkennen. Basis-DN(s) und Import-Filter prüfen. | diff --git a/docs/anleitung-anwender.md b/docs/anleitung-anwender.md index 86a1d6c..4d0b444 100644 --- a/docs/anleitung-anwender.md +++ b/docs/anleitung-anwender.md @@ -41,7 +41,15 @@ Rufen Sie die Anmeldeseite auf. Auf einem großen Bildschirm ist sie geteilt: li Über den Link „Passwort vergessen?" können Sie ein neues Passwort per E-Mail anfordern — dieser Weg funktioniert nur für Konten, deren Passwort lokal in Tessera verwaltet wird (siehe [Persönliche Einstellungen](#persönliche-einstellungen)). -Falls Ihr Administrator beim Anlegen Ihres Kontos eine Passwort-Änderung erzwungen hat, werden Sie nach der Anmeldung automatisch aufgefordert, ein neues Passwort zu vergeben, bevor Sie fortfahren können. +Der Ablauf von „Passwort vergessen?" im Einzelnen: + +1. Es öffnet sich die Seite **Passwort zurücksetzen**. Tragen Sie dort Ihre **E-Mail-Adresse** ein und klicken Sie auf **Link senden**. Tessera antwortet immer mit dem Satz „Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet." — das ist Absicht, damit niemand durch Ausprobieren herausfinden kann, welche Adressen bei Tessera bekannt sind. Über **Zurück zur Anmeldung** gelangen Sie wieder zum Anmeldeformular. +2. Öffnen Sie die E-Mail und klicken Sie auf den darin enthaltenen Link. Es erscheint die Seite **Neues Passwort festlegen** mit den Feldern **Neues Passwort** und **Passwort bestätigen**. Das neue Passwort muss mindestens acht Zeichen lang sein, und beide Eingaben müssen übereinstimmen; sonst erscheint der Hinweis „Die Passwörter stimmen nicht überein." +3. Klicken Sie auf **Passwort zurücksetzen**. Nach der Meldung „Ihr Passwort wurde erfolgreich zurückgesetzt." leitet Tessera Sie nach wenigen Sekunden automatisch zur Anmeldung weiter, wo Sie sich mit dem neuen Passwort anmelden. + +Ist der Link zu alt, schon einmal benutzt oder ungültig, nennt Tessera genau das („Der Link zum Zurücksetzen ist abgelaufen.", „Dieser Link wurde bereits verwendet." beziehungsweise „Der Link zum Zurücksetzen ist ungültig."). Fordern Sie dann über „Passwort vergessen?" einfach einen neuen Link an. + +Falls Ihr Administrator beim Anlegen Ihres Kontos eine Passwort-Änderung erzwungen hat, werden Sie nach der Anmeldung automatisch auf die Seite **Passwort ändern** geleitet und können erst weiterarbeiten, wenn Sie ein neues Passwort vergeben haben. Oben auf der Seite steht der Hinweis „Aus Sicherheitsgründen müssen Sie Ihr Passwort ändern, bevor Sie fortfahren können." Tragen Sie Ihr **Aktuelles Passwort** (das Passwort, das Ihnen Ihr Administrator mitgeteilt hat) und zweimal das neue Passwort ein (mindestens acht Zeichen; die Felder heißen **Neues Passwort** und **Neues Passwort bestätigen**) und klicken Sie auf **Passwort ändern**. Danach öffnet sich Ihr Dashboard. Stimmt das aktuelle Passwort nicht, meldet Tessera „Das aktuelle Passwort ist falsch." ## Aufbau der Oberfläche @@ -53,8 +61,7 @@ Die schmale dunkle Leiste am oberen Rand. Links steht das Tessera-Logo, in der M - Einen Schalter zum Umschalten zwischen hellem und dunklem Erscheinungsbild (siehe [Persönliche Einstellungen](#persönliche-einstellungen)). - Ihr Benutzersymbol (Avatar oder Ihr Anfangsbuchstabe). Ein Klick öffnet das **Benutzermenü** mit: - Ihrem Namen und Ihrer Rolle (Benutzer, Admin oder Super-Admin), - - **Einstellungen** — führt zu Ihren persönlichen Einstellungen, - - **Administrator** — erscheint nur, wenn Sie Admin- oder Super-Admin-Rechte haben, + - **Einstellungen** — führt zu Ihren persönlichen Einstellungen. Bei Administratoren heißt dieser Eintrag **Einstellungen/Administration**; von dort erreichen sie in der Leiste der Einstellungen auch die Administration (siehe [Persönliche Einstellungen](#persönliche-einstellungen)), - **Abmelden**. **Seitenleiste (links)** @@ -98,6 +105,7 @@ Ihre Änderungen werden über **„Fertig"** übernommen. Verlassen Sie den Bear | XFrame | Zeigt eine Webseite als Rahmen in der Kachel. Die https-Adresse, einen optionalen Titel und ob die Seite automatisch neu geladen wird (nie, 1 Minute bis 1 Stunde), stellen Sie unter Einstellungen → Widgets ein. Die eingebettete Seite kann Tessera nicht verlassen; über „In neuem Tab öffnen“ erreichen Sie die Seite jederzeit direkt. Manche Webseiten erlauben das Einbetten nicht — der Rahmen bleibt dann leer, der Knopf funktioniert trotzdem. Wahlweise zeigen Sie nur einen Ausschnitt der Seite: den Rahmen in der Vorschau verschieben oder an den Ecken ziehen (oder Links, Oben, Breite und Höhe eintippen) – die Kachel zeigt dann genau diesen Ausschnitt, passend zu ihrer Größe; für die ganze Seite gibt es eine Vergrößerung (50 bis 150 %), und „Nur anzeigen“ sperrt Klicken und Scrollen im Rahmen. | | Proxmox | Zeigt den Zustand Ihrer Proxmox-Server: oben ein farbiger Balken mit „Alles in Ordnung“ oder zum Beispiel „1 nicht erreichbar, 1 mit Warnung“, darunter die Server, auffällige zuerst, jeweils mit einer Kennzahl (laufende Gäste, gestoppte Gäste mit Autostart, Auslastung bei einer Warnung, letzte Sicherung oder eingehende E-Mails). Ein Klick auf einen Server öffnet die Proxmox-Seite. Die Kachel steht nur Benutzern zur Verfügung, die das Proxmox-Modul nutzen dürfen. Sie aktualisiert sich jede Minute aus dem zuletzt gespeicherten Stand und fragt die Server dabei nicht neu ab. Einen Titel und die Auswahl der angezeigten Server (ohne Auswahl: alle) legen Sie im Bearbeitungsmodus direkt an der Kachel über das Titelfeld und „Server auswählen“ fest oder unter Einstellungen → Widgets. In einer schmalen Kachel stehen nur Punkte und Namen, in einer sehr kleinen nur Balken und Zusammenfassung | | Erinnerungen | Persönliche Erinnerungen mit Datum, Uhrzeit, Titel und Beschreibung. Über „Neue Erinnerung“ legen Sie eine an; die Liste zeigt Ihre Erinnerungen nach Zeit sortiert. Zur gewählten Zeit meldet sich Tessera mit einer Benachrichtigung – im Browser nach einer einmaligen Erlaubnis (der Browser fragt beim ersten Anlegen), in der Desktop-App als Windows-Benachrichtigung, auch wenn das Fenster im Infobereich liegt. Blockiert Ihr Browser Benachrichtigungen, sehen Sie die fällige Erinnerung nur hier in der Kachel; ein Hinweis in der Kachel sagt das. Wer möchte, setzt beim Anlegen den Haken „Zusätzlich per E-Mail erinnern“: Tessera schickt dann zur gewählten Zeit eine E-Mail an Ihre Adresse, auch wenn Tessera nirgends geöffnet ist. Der Haken ist ausgegraut, wenn Ihr Administrator noch keinen E-Mail-Versand eingerichtet hat oder in Ihrem Konto keine E-Mail-Adresse hinterlegt ist; die Kachel nennt den Grund. Eine fällige Erinnerung bleibt hervorgehoben mit „Fällig“ stehen. „Erledigt“ entfernt sie; „Später erinnern“ verschiebt sie auf in 10 Minuten, in 1 Stunde oder morgen zur gleichen Uhrzeit — dann melden sich Benachrichtigung und (falls gewählt) E-Mail noch einmal. Bearbeiten und Löschen sind nur möglich, solange die Erinnerung noch nicht fällig ist. Erinnerungen sind persönlich: nur Sie sehen und ändern Ihre | +| Nextcloud-Status | Ampelübersicht Ihrer Nextcloud-Clouds: drei Zähler für Grün, Gelb und Rot (bei Bedarf „Ohne Bewertung“) und darunter die roten und gelben Clouds mit Kundenname und Grund. Die Kachel steht nur Benutzern zur Verfügung, die das Modul Nextcloud-Status nutzen dürfen, und liest nur den gespeicherten Stand, ohne selbst eine Prüfung auszulösen. Ein Klick öffnet das Modul, siehe [Nextcloud-Status](#nextcloud-status) | Für Uhr, Suchleiste, Kalender, Notizen, Favoriten, Bilderrahmen, XFrame und Proxmox gibt es zusätzliche Einstellungen (z. B. Zeitzone und Schriftgröße der Uhr, eigene Suchanbieter, Kalenderquellen, Überschrift der Notiz- und Favoriten-Kachel, Bilder und Wechselintervall des Bilderrahmens, Adresse, Titel und Neuladen des XFrame, Titel und angezeigte Server der Proxmox-Kachel) — diese finden Sie unter **Einstellungen → Widgets**, siehe [Persönliche Einstellungen](#persönliche-einstellungen). @@ -107,8 +115,9 @@ Für Uhr, Suchleiste, Kalender, Notizen, Favoriten, Bilderrahmen, XFrame und Pro - **Aktiviert** — das Modul wurde vom Administrator für Ihr Unternehmen freigeschaltet. - **Verfügbar** — das Modul existiert, ist aber noch nicht aktiviert. +- **Kein Zugriff** — dieser Hinweis erscheint zusätzlich zu „Aktiviert“, wenn das Modul für Ihr Unternehmen aktiv ist, Sie persönlich aber keine Freigabe haben. Die Kachel wirkt dann abgeblendet. Klicken Sie darauf, öffnet sich keine Detailseite, sondern Tessera zeigt kurz die Meldung „Kein Zugriff auf dieses Modul — wenden Sie sich an Ihren Administrator." -Über die Suche und die Filter (Status, Kategorie) lässt sich die Liste eingrenzen. Ein Klick auf eine Kachel öffnet die Detailseite mit einer ausführlicheren Beschreibung. Darunter steht der Abschnitt **„Änderungen“**: Er zeigt, was sich von Modulversion zu Modulversion geändert hat, gegliedert in Neu, Geändert und Behoben. Die neueste Version steht oben, ist mit „Aktuell“ gekennzeichnet und aufgeklappt; ältere Versionen klappen Sie mit einem Klick auf. +Über die Suche und die Filter (Status, Kategorie) lässt sich die Liste eingrenzen. Ein Klick auf eine Kachel öffnet die Detailseite mit einer ausführlicheren Beschreibung. Dort sehen Sie, ob das Modul **Aktiviert** oder **Nicht aktiviert** ist; über **Zurück zum Marktplatz** kehren Sie zur Übersicht zurück. Darunter steht der Abschnitt **„Änderungen“**: Er zeigt, was sich von Modulversion zu Modulversion geändert hat, gegliedert in Neu, Geändert und Behoben. Die neueste Version steht oben, ist mit „Aktuell“ gekennzeichnet und aufgeklappt; ältere Versionen klappen Sie mit einem Klick auf. **Aktivieren und Freigeben sind zwei unterschiedliche Dinge:** Nur Administratoren können ein Modul für das gesamte Unternehmen **aktivieren**. Ob Sie persönlich das Modul danach auch sehen und öffnen können, hängt zusätzlich davon ab, ob Ihnen oder Ihrer Gruppe der Zugriff **freigegeben** wurde. Ein Modul kann also für das Unternehmen aktiv sein, ohne dass Sie selbst Zugriff darauf haben. Versuchen Sie in diesem Fall, das Modul zu öffnen, erscheint statt des Moduls die Meldung „Kein Zugriff auf dieses Modul" mit dem Hinweis „Sie haben für dieses Modul keine Freigabe. Wenden Sie sich an Ihren Administrator." In diesem Fall wenden Sie sich an Ihren Administrator, damit er Ihnen (direkt oder über eine Gruppe) die Freigabe für das Modul erteilt. @@ -116,7 +125,7 @@ Eine Freigabe hat zwei Stufen: **Benutzen** (das Modul öffnen und damit arbeite ## Die Module -Aktuell stehen in Tessera sieben Module zur Verfügung. Je nachdem, welche für Sie freigegeben sind, sehen Sie sie in der Seitenleiste unter ihrer jeweiligen Kategorie. +Aktuell stehen in Tessera zehn Module zur Verfügung. Je nachdem, welche für Sie freigegeben sind, sehen Sie sie in der Seitenleiste unter ihrer jeweiligen Kategorie. ### Ausschreibungs-Radar @@ -149,11 +158,11 @@ Das Modul prüft automatisch ein Postfach auf eingehende DKV-Tankkarten-Rechnung ### Zertifikatsmanager -Der Zertifikatsmanager hilft Ihnen, Zertifikate, Schlüssel und Zertifikatsanfragen zu prüfen, zu ordnen und in das Format zu bringen, das Ihr Server braucht. Er arbeitet mit **einer gemeinsamen Liste von Dateien**: Sie laden Ihre Dateien einmal im ersten Reiter hoch, und alle anderen Reiter arbeiten mit dieser Liste. Es gibt sechs Reiter, in dieser Reihenfolge: **Dateien**, **Analysieren**, **Aufteilen**, **Zusammenführen**, **Konvertieren** und **Vorlagen**. Ist die Liste noch leer, weisen die anderen Reiter freundlich auf den Reiter „Dateien“ hin. Wenn Sie zwischen den Reitern wechseln, bleibt die Liste erhalten. +Der Zertifikatsmanager hilft Ihnen, Zertifikate, Schlüssel und Zertifikatsanfragen zu prüfen, zu ordnen und in das Format zu bringen, das Ihr Server braucht. Er arbeitet mit **einer gemeinsamen Liste von Dateien**: Sie laden Ihre Dateien einmal im ersten Reiter hoch, und alle anderen Reiter arbeiten mit dieser Liste. Es gibt sechs Reiter, in dieser Reihenfolge: **Dateien**, **Analysieren**, **Aufteilen**, **Zusammenführen**, **Konvertieren** und **Vorlagen**. Ist die Liste noch leer, weisen die anderen Reiter freundlich auf den Reiter „Dateien“ hin. Wenn Sie zwischen den Reitern wechseln, bleibt die Liste erhalten. Sobald Dateien in der Liste liegen, zeigt der erste Reiter die Anzahl an, zum Beispiel **Dateien (3)**. **Wichtig zu Schlüsseln und Passwörtern:** Die Liste lebt nur im Arbeitsspeicher Ihres Browserfensters. Private Schlüssel und Passwörter werden von Tessera nirgends gespeichert und in keinem Protokoll festgehalten. Laden Sie die Seite neu oder schließen Sie das Fenster, ist die Liste weg, und Sie laden Ihre Dateien bei Bedarf noch einmal hoch. -- **Dateien** — Hier sammeln Sie alles. Ziehen Sie Dateien in das Feld oder klicken Sie hinein und wählen Sie Dateien aus, so oft und in beliebiger Menge nacheinander; **jede neue Datei kommt zur Liste hinzu und ersetzt nie eine frühere**. Sie können auch die ZIP-Datei Ihres Zertifikatsausstellers hochladen: Tessera schaut hinein und nennt zu jedem enthaltenen Eintrag, was es darin gefunden hat. Überflüssiges wie Ordner mit dem Namen `__MACOSX` überspringt Tessera still; verschachtelte oder passwortgeschützte ZIP-Dateien nennt es mit dem Grund, warum es sie nicht öffnet. Zusätzlich können Sie PEM-Text einfügen („PEM-Text einfügen“, Text beginnt mit `-----BEGIN`); er erscheint als eigener Eintrag. Die Liste fasst bis zu 30 Einträge, höchstens 5 MB je Datei und zusammen höchstens 10 MB; dieselbe Datei kann nicht zweimal hinzugefügt werden. Auf einmal prüft Tessera höchstens 200 Zertifikate, 50 Schlüssel und 50 Zertifikatsanfragen; steckt mehr in Ihren Dateien, erscheint ein Hinweis, und Sie nehmen einzelne Dateien aus der Liste. Zu jedem Eintrag zeigt die Liste, was erkannt wurde: Serverzertifikat, Zwischenzertifikat, Stammzertifikat, privater Schlüssel oder Zertifikatsanfrage. Mit dem Knopf am Eintrag nehmen Sie eine einzelne Datei wieder aus der Liste. Ist eine Datei durch ein Passwort geschützt (PFX-/P12-Datei oder verschlüsselter Schlüssel), erscheint beim Eintrag ein Feld für das Passwort; es gilt nur für diese eine Datei und darf Umlaute und Sonderzeichen enthalten. Ein Passwortschutz, den zu prüfen übermäßig aufwendig wäre (mehr als eine Million Rechenschritte, wie sie nur eine absichtlich präparierte Datei verlangt), wird mit einem Hinweis beim Eintrag übersprungen. Hat Tessera zum Serverzertifikat schon einen Schlüssel, erscheint eine passwortgeschützte PFX-Datei nur als ruhiger Hinweis, den Sie nicht beachten müssen. +- **Dateien** — Hier sammeln Sie alles. Ziehen Sie Dateien in das Feld oder klicken Sie hinein und wählen Sie Dateien aus, so oft und in beliebiger Menge nacheinander; **jede neue Datei kommt zur Liste hinzu und ersetzt nie eine frühere**. Sie können auch die ZIP-Datei Ihres Zertifikatsausstellers hochladen: Tessera schaut hinein und nennt zu jedem enthaltenen Eintrag, was es darin gefunden hat. Überflüssiges wie Ordner mit dem Namen `__MACOSX` überspringt Tessera still; verschachtelte oder passwortgeschützte ZIP-Dateien nennt es mit dem Grund, warum es sie nicht öffnet. Zusätzlich können Sie PEM-Text einfügen („PEM-Text einfügen“, Text beginnt mit `-----BEGIN`); er erscheint als eigener Eintrag. Die Liste fasst bis zu 30 Einträge, höchstens 5 MB je Datei und zusammen höchstens 10 MB; dieselbe Datei kann nicht zweimal hinzugefügt werden. Auf einmal prüft Tessera höchstens 200 Zertifikate, 50 Schlüssel und 50 Zertifikatsanfragen; steckt mehr in Ihren Dateien, erscheint ein Hinweis, und Sie nehmen einzelne Dateien aus der Liste. Zu jedem Eintrag zeigt die Liste, was erkannt wurde: Serverzertifikat, Zwischenzertifikat, Stammzertifikat, privater Schlüssel oder Zertifikatsanfrage. Mit dem Knopf am Eintrag nehmen Sie eine einzelne Datei wieder aus der Liste; über **Alle entfernen** über der Liste leeren Sie die ganze Liste mit einem Klick. Ist eine Datei durch ein Passwort geschützt (PFX-/P12-Datei oder verschlüsselter Schlüssel), erscheint beim Eintrag ein Feld für das Passwort; es gilt nur für diese eine Datei und darf Umlaute und Sonderzeichen enthalten. Ein Passwortschutz, den zu prüfen übermäßig aufwendig wäre (mehr als eine Million Rechenschritte, wie sie nur eine absichtlich präparierte Datei verlangt), wird mit einem Hinweis beim Eintrag übersprungen. Hat Tessera zum Serverzertifikat schon einen Schlüssel, erscheint eine passwortgeschützte PFX-Datei nur als ruhiger Hinweis, den Sie nicht beachten müssen. - **Analysieren** — Zeigt zu jedem erkannten Teil die Einzelheiten: Name, Aussteller, Gültigkeit (mit den verbleibenden Tagen), Schlüsselart (RSA oder EC mit Kurve), die Namen, für die das Zertifikat gilt, sowie Seriennummer und Fingerabdrücke. Außerdem sehen Sie, was zusammengehört: welcher Schlüssel zu welchem Zertifikat passt und welche Zertifikatsanfrage zu welchem Zertifikat gehört. Tessera ordnet nie nach dem Namen zu, sondern prüft den Schlüssel selbst. - **Aufteilen** — Zerlegt Ihre Dateien in die einzelnen Teile. Jedes Teil laden Sie in seinem natürlichen Format herunter (Zertifikat als `.crt`, Schlüssel als `.key`, Anfrage als `.csr`) oder alle zusammen als ZIP-Datei. **Private Schlüssel bleiben geschützt:** War ein Schlüssel in Ihrer Datei durch ein Passwort geschützt, ist oben im Reiter „Schlüssel mit einem Passwort schützen“ schon angekreuzt; Sie geben ein Passwort ein (es darf ein anderes als das alte sein), und die heruntergeladenen Schlüsseldateien sind damit verschlüsselt, auch in der ZIP-Datei. Solange das Passwort fehlt, sind die Knöpfe für die Schlüssel und für die ZIP-Datei gesperrt; die Zertifikate laden Sie trotzdem herunter. Möchten Sie die Schlüssel bewusst ohne Schutz speichern, nehmen Sie das Häkchen heraus; Tessera weist dann ausdrücklich darauf hin, dass die Schlüsseldateien unverschlüsselt sind. Jeder Schlüssel trägt den Namen seines Zertifikats (`name.key`), gibt es keines, wird nummeriert. - **Zusammenführen** — Tessera ordnet Serverzertifikat, Zwischenzertifikate und Stammzertifikat **selbst**; die Reihenfolge Ihrer Dateien spielt keine Rolle. Dabei prüft es nicht nur Namen, sondern die echte Unterschrift jedes Zertifikats, sodass ein gleichnamiges, aber falsches Zwischenzertifikat nie verwendet wird. Gibt es mehrere Möglichkeiten, wählt Tessera nachvollziehbar die beste (zum Beispiel nicht abgelaufen). Sie können herunterladen: **Fullchain** (Serverzertifikat plus Zwischenzertifikate), **Nur Kette** (nur die Zwischenzertifikate, zum Beispiel für Systeme, die das Serverzertifikat getrennt wollen), **Zertifikat und Schlüssel** in einer PEM-Datei sowie eine **PFX-Datei**. Fullchain und Nur Kette gibt es als PEM, als `.p7b` oder als binäre `.p7c`. Für die PFX-Datei vergeben Sie ein Passwort (zweimal eingeben; Umlaute und Sonderzeichen sind erlaubt, die Datei lässt sich damit auch mit Windows, OpenSSL und Java öffnen) und wählen die Verschlüsselung: **„Kompatibel (auch ältere Windows-Server)“** ist vorgewählt und die sichere Wahl, wenn Sie nicht wissen, wo die Datei eingespielt wird; **„Modern (AES-256)“** ist stärker, wird aber von älteren Systemen wie Windows Server 2016 oft nicht gelesen. Das Häkchen **„Root-Zertifikat mitnehmen“** ist standardmäßig aus, denn die meisten Server und Browser kennen die Wurzel schon und brauchen sie nicht; setzen Sie es nur, wenn ein Gerät es ausdrücklich verlangt (manche Geräte, Java-Anwendungen oder eigene Firmenwurzeln). Fehlt ein Aussteller in Ihrer Liste, sagt Tessera das ausdrücklich: **„Zwischenzertifikat fehlt“** heißt, dass direkt über dem Serverzertifikat das Zwischenzertifikat nicht in der Liste ist; fehlt dagegen nur das Zertifikat ganz oben (meist die Wurzel), erscheint ein ruhiger Hinweis, denn das ist für die meisten Server in Ordnung. Das fehlende Zertifikat können Sie selbst im Reiter „Dateien“ hinzufügen oder, wie unten beschrieben, von Tessera holen lassen. @@ -223,7 +232,7 @@ Das Modul zeigt den Zustand Ihrer Proxmox-Server auf einen Blick — für die dr **„Verbindung testen"** prüft den hinterlegten Zugang gegen den echten Server, ohne die zuletzt gemessenen Werte auf der Modulseite zu überschreiben. Bei Erfolg erscheint eine grüne Bestätigung; scheitert der Test, nennt die Meldung die Ursache in Alltagssprache — etwa „nicht erreichbar", „Zugang abgelehnt", „Rechte reichen nicht" oder ein Zertifikatsproblem. -**Die Dashboard-Kachel „Proxmox“:** Wer das Modul nutzen darf, kann sich den Zustand der Server auch als Kachel auf das Dashboard legen („Widget hinzufügen“, ganz unten im Katalog). Sie zeigt den farbigen Balken der Modulseite in klein, darunter in Worten, ob alles in Ordnung ist, und die Server mit je einer Kennzahl; ein Klick auf einen Server öffnet diese Seite. Die Kachel liest jede Minute den zuletzt gespeicherten Stand und löst selbst nie eine Abfrage bei Proxmox aus — wie aktuell die Werte sind, bestimmt weiterhin der Abfrageabstand je Server. Welche Server die Kachel zeigt, stellen Sie im Bearbeitungsmodus an der Kachel oder unter Einstellungen → Widgets ein. +**Die Dashboard-Kachel „Proxmox“:** Wer das Modul nutzen darf, kann sich den Zustand der Server auch als Kachel auf das Dashboard legen („Widget hinzufügen“). Sie zeigt den farbigen Balken der Modulseite in klein, darunter in Worten, ob alles in Ordnung ist, und die Server mit je einer Kennzahl; ein Klick auf einen Server öffnet diese Seite. Die Kachel liest jede Minute den zuletzt gespeicherten Stand und löst selbst nie eine Abfrage bei Proxmox aus — wie aktuell die Werte sind, bestimmt weiterhin der Abfrageabstand je Server. Welche Server die Kachel zeigt, stellen Sie im Bearbeitungsmodus an der Kachel oder unter Einstellungen → Widgets ein. ### Nextcloud-Status @@ -312,11 +321,11 @@ Das Modul zeigt Ihre Dateien aus der Firmen-Nextcloud direkt in Tessera: ansehen ### Kantinenabrechnung -Das Modul bereitet die Kantinenabrechnung für die Gehaltsabrechnung vor. Es steht in der Seitenleiste in der Gruppe **Finanzbuchhaltung**. Sie laden die CSV-Datei der Kantine hoch, Tessera prüft sie und erstellt daraus die Lohndatei für DATEV. +Das Modul bereitet die Kantinenabrechnung für die Gehaltsabrechnung vor. Es steht in der Seitenleiste in der Gruppe **Finanzbuchhaltung** und hat zwei Reiter: **Abrechnung** und **Einstellungen**. Sie laden die CSV-Datei der Kantine hoch, Tessera prüft sie und erstellt daraus die Lohndatei für DATEV. **Einmalig einrichten (Administrator oder wer das Modul verwalten darf):** Im Reiter **Einstellungen** trägt ein Administrator Beraternummer, Mandantennummer und Lohnart ein (nur Ziffern). Diese Angaben gelten für alle Benutzer Ihres Unternehmens. Solange sie fehlen, zeigt das Modul einen Hinweis und der Download bleibt gesperrt; andere Benutzer sehen den Hinweis „Ein Administrator oder jemand, der dieses Modul verwalten darf, muss zuerst Beraternummer, Mandantennummer und Lohnart hinterlegen.“ -**Abrechnung:** Ziehen Sie die CSV-Datei auf die Fläche oder klicken Sie darauf. Die Datei muss Semikolon-getrennt sein und elf Spalten haben; ob sie als UTF-8 oder Windows-1252 gespeichert ist, erkennt Tessera selbst. Nach dem Hochladen sehen Sie: +**Reiter „Abrechnung“:** Ziehen Sie die CSV-Datei auf die Fläche oder klicken Sie darauf. Die Datei muss Semikolon-getrennt sein und elf Spalten haben; ob sie als UTF-8 oder Windows-1252 gespeichert ist, erkennt Tessera selbst. Nach dem Hochladen sehen Sie: - die **Zeilenzahl**, den **Abrechnungsmonat** (aus der Spalte „Abrechnung bis“) und den **Gesamtbetrag**, - **Hinweise**, zum Beispiel wenn die Datei verschiedene Abrechnungsmonate enthält, @@ -326,13 +335,13 @@ Ist alles in Ordnung, erzeugt **„DATEV-Datei herunterladen“** die Lohndatei ### Handelsware -Das Modul ordnet Handelswaren-Umsätze aus einer Excel-Liste den Erlöskonten zu und erstellt die Buchungsdatei für DATEV. Es steht in der Gruppe **Finanzbuchhaltung**. Es hat drei Reiter. +Das Modul ordnet Handelswaren-Umsätze aus einer Excel-Liste den Erlöskonten zu und erstellt die Buchungsdatei für DATEV. Es steht in der Gruppe **Finanzbuchhaltung**. Es hat drei Reiter: **Import**, **Konten** und **Einstellungen**. **Einmalig einrichten (Administrator oder wer das Modul verwalten darf):** Im Reiter **Einstellungen** legt ein Administrator das **Standard-Erlöskonto** (wird neuen Konten zugeordnet) und den **Startwert Gegenkonto** (die erste Nummer, die vergeben wird, wenn die Kontenliste leer ist) fest. Solange das fehlt, ist die Verarbeitung gesperrt; andere Benutzer sehen den Hinweis, dass ein Administrator oder jemand, der das Modul verwalten darf, das zuerst hinterlegen muss. -**Import:** Laden Sie die Excel-Datei (.xlsx) hoch. Der Buchungstext steht in Spalte A, der Umsatz in Spalte B (als Zahl oder als Text wie „1.234,56“), der Kopftext in Zelle B1. Die Vorschau zeigt je Zeile Buchungstext, Umsatz, S/H (Soll oder Haben), Gegenkonto, Datum und Erlöskonto. Produkte, die noch kein Konto haben, tragen die Markierung **neu** und bekommen das nächste freie Gegenkonto. Das **Buchungsdatum** wird aus dem Dateinamen abgeleitet („HWA 0326 Test.xlsx“ ergibt den letzten Tag im März 2026, also 3103); Sie können es als vierstelliges TTMM ändern, eine ungültige Eingabe sperrt den Download. **„Buchungsdatei herunterladen“** erstellt die TXT-Datei und speichert erst jetzt die neuen Konten. Hat sich die Kontenliste zwischenzeitlich geändert, weist Tessera darauf hin und bietet an, die Vorschau neu zu laden. +**Reiter „Import“:** Laden Sie die Excel-Datei (.xlsx) hoch. Der Buchungstext steht in Spalte A, der Umsatz in Spalte B (als Zahl oder als Text wie „1.234,56“), der Kopftext in Zelle B1. Die Vorschau zeigt je Zeile Buchungstext, Umsatz, S/H (Soll oder Haben), Gegenkonto, Datum und Erlöskonto. Produkte, die noch kein Konto haben, tragen die Markierung **neu** und bekommen das nächste freie Gegenkonto. Das **Buchungsdatum** wird aus dem Dateinamen abgeleitet („HWA 0326 Test.xlsx“ ergibt den letzten Tag im März 2026, also 3103); Sie können es als vierstelliges TTMM ändern, eine ungültige Eingabe sperrt den Download. **„Buchungsdatei herunterladen“** erstellt die TXT-Datei und speichert erst jetzt die neuen Konten. Hat sich die Kontenliste zwischenzeitlich geändert, weist Tessera darauf hin und bietet an, die Vorschau neu zu laden. -**Konten:** Hier sehen Sie die Kontenliste (Name, Gegenkonto, Erlöskonto) und können Konten hinzufügen, bearbeiten und löschen. **„CSV importieren“** liest eine Datei im Format `Name;Gegenkonto;Konto` ein und **ersetzt alle vorhandenen Konten** — vorher fragt Tessera nach; enthält die Datei Fehler, wird nichts geändert. **„CSV exportieren“** speichert die Liste als CSV-Datei, die sich in Excel öffnen lässt. Die Kontenliste dürfen alle Benutzer mit Zugriff auf das Modul pflegen. +**Reiter „Konten“:** Hier sehen Sie die Kontenliste (Name, Gegenkonto, Erlöskonto) und können Konten hinzufügen, bearbeiten und löschen. **„CSV importieren“** liest eine Datei im Format `Name;Gegenkonto;Konto` ein und **ersetzt alle vorhandenen Konten** — vorher fragt Tessera nach; enthält die Datei Fehler, wird nichts geändert. **„CSV exportieren“** speichert die Liste als CSV-Datei, die sich in Excel öffnen lässt. Die Kontenliste dürfen alle Benutzer mit Zugriff auf das Modul pflegen. ## Persönliche Einstellungen @@ -458,4 +467,4 @@ Sie schließen das Fenster mit **Verstanden**, mit dem Kreuz oben rechts, mit de - **Die Seitenleiste zeigt „Keine Module".** Für Sie sind noch keine Module freigegeben. Das ist normal für neu angelegte Konten — wenden Sie sich an Ihren Administrator. - **Im Ausschreibungs-Radar erscheinen nur sehr wenige Treffer.** Aktuell werden nur EU-weite Oberschwellen-Ausschreibungen erfasst; kleinere Unterschwellen-Vergaben fehlen noch. Das Hinweisbanner auf der Modulseite erklärt das. - **Der Knopf „Fehler melden" antwortet, es sei kein Postfach eingerichtet.** Ihr Administrator hat unter Administration → E-Mail-Versand (SMTP) noch keine Adresse im Feld „Fehlermeldungen an" hinterlegt. Sprechen Sie ihn an – die Meldung selbst geht dabei nicht verloren, Sie können sie danach erneut senden. -- **Zahlen, die Sie über „Meine Quellen" im Ausschreibungs-Radar eingebracht haben, tauchen in der Trefferliste aller Kollegen auf.** Das ist beabsichtigt — die Trefferliste ist für das ganze Unternehmen gemeinsam, nicht postfachbezogen getrennt. +- **Ausschreibungen, die über Ihr Postfach oder Ihre Feeds („Meine Quellen“) hereinkommen, tauchen in der Trefferliste aller Kolleginnen und Kollegen auf.** Das ist beabsichtigt — die Trefferliste ist für das ganze Unternehmen gemeinsam, nicht postfachbezogen getrennt. diff --git a/docs/anleitung-betrieb.md b/docs/anleitung-betrieb.md index 050fdcf..c570c28 100644 --- a/docs/anleitung-betrieb.md +++ b/docs/anleitung-betrieb.md @@ -66,7 +66,14 @@ Der Browser spricht ausschließlich mit `web` (Port 3000). Aufrufe unter Der Browser erreicht die API also nie direkt und muss auch keine eigene CORS-Freigabe für die API-Origin haben – Port 3001 muss von außen in der Regel nicht erreichbar sein, ist es in den mitgelieferten Compose-Dateien aber (Host-Port 3001 ist -gemappt). +gemappt, sowohl in `docker-compose.yml` als auch in `docker-compose.prod.yml`). + +**Empfehlung zur Firewall:** Sperren Sie Port 3001 in der Firewall des Servers (bzw. +lassen Sie ihn nur vom Server selbst zu) und geben Sie nach außen ausschließlich +Port 3000 frei, im Normalfall über den Nginx Proxy Manager. Der Port 3001 wird für +den normalen Betrieb nicht gebraucht, weil der Browser die API immer über `web` +und `/api-proxy` erreicht. Die Abfragen mit `curl` auf `http://localhost:3001/...` +in dieser Anleitung laufen auf dem Server selbst und bleiben davon unberührt. ## 2. Erstinstallation @@ -153,15 +160,21 @@ Zugangsdaten. | `DB_PASSWORD` | ja | – | Passwort des PostgreSQL-Benutzers `tessera`, an den `db`-Container durchgereicht. | | `DATABASE_URL` | ja | – | Vollständiger Prisma-Connection-String, z. B. `postgresql://tessera:@db:5432/tessera`. Muss zum `DB_PASSWORD` passen. | | `JWT_SECRET` | ja | – | Signiert/prüft die JWT-Auth-Cookies. Wird **sowohl** an `api` **als auch** an `web` durchgereicht – beide müssen denselben Wert erhalten, sonst schlägt die Anmeldung fehl. In der Basis-Compose (Dev) gibt es einen unsicheren Default (`tessera-dev-jwt-secret-change-in-production`), in Prod nicht. | -| `TESSERA_ENCRYPTION_KEY` | ja | – | AES-256-GCM-Schlüssel (64 Hex-Zeichen) für gespeicherte Drittanbieter-Zugangsdaten. Siehe Kapitel 2. `CALENDAR_ENCRYPTION_KEY` ist der alte Variablenname und wird weiterhin akzeptiert (mit Warnung im Log). | +| `TESSERA_ENCRYPTION_KEY` | ja | – | AES-256-GCM-Schlüssel (64 Hex-Zeichen) für gespeicherte Drittanbieter-Zugangsdaten. Siehe Kapitel 2. | +| `CALENDAR_ENCRYPTION_KEY` | nein | leer | Alter Name von `TESSERA_ENCRYPTION_KEY` (aus der Zeit, als der Schlüssel nur den Kalender schützte). Wird nur noch aus Kompatibilität gelesen: Ist `TESSERA_ENCRYPTION_KEY` nicht gesetzt, übernimmt die API diesen Wert und schreibt beim Start eine Warnung ins Protokoll. Ist `TESSERA_ENCRYPTION_KEY` gesetzt, hat er Vorrang. Eine bestehende `.env` mit dem alten Namen funktioniert also weiter; für neue Installationen immer `TESSERA_ENCRYPTION_KEY` verwenden. | +| `TESSERA_MIGRATE_DATABASE_URL` | nein | leer | Eigene Datenbankverbindung, die ausschließlich der Migrationsschritt beim Start des `api`-Containers benutzt (Kapitel 5). Sie ist dafür gedacht, dass die Migration mit den Rechten des Tabelleneigentümers läuft, die laufende Anwendung aber über `DATABASE_URL` mit einer rechteärmeren Rolle arbeitet. Leer (der Normalfall) heißt: Migration und Anwendung nutzen beide `DATABASE_URL`. Erst belegen, wenn die Schritte in `docs/mandantentrennung-datenbankrolle.md` vollständig abgearbeitet sind – eine voreilige Umstellung sperrt die Anwendung von ihren eigenen Daten aus. | | `TESSERA_ADMIN_USER` | nein | `admin` | Benutzername des initialen Super-Admin, nur beim allerersten Start relevant. | | `TESSERA_ADMIN_EMAIL` | ja (für den Seed) | – | E-Mail des initialen Super-Admin. | | `TESSERA_ADMIN_PASSWORD` | ja (für den Seed) | – | Initiales Passwort des Super-Admin. | | `TESSERA_FORCE_CHANGE` | nein | `true` | Erzwingt Passwortwechsel beim ersten Login des geseedeten Admin-Accounts. | -| `TESSERA_SMTP_HOST` / `_PORT` / `_SECURE` / `_USER` / `_PASSWORD` / `_FROM` | nein (aber ohne Host kein Mailversand) | Host leer, Port `587`, `_SECURE=false` | SMTP-Relay für ausgehende Mails (Passwort-Reset, Benachrichtigungen). | +| `TESSERA_SMTP_HOST` / `_PORT` / `_SECURE` / `_USER` / `_PASSWORD` / `_FROM` | nein (aber ohne Host kein Mailversand) | Host leer, Port `587`, `_SECURE=false`, `_FROM` = `Tessera ` | SMTP-Relay für ausgehende Mails (Passwort-Reset, Benachrichtigungen). Gilt nur als Rückfall: Sobald unter Administration → E-Mail-Versand (SMTP) ein Versandweg eingerichtet ist, verwendet Tessera diesen und ignoriert die Variablen. Der Standard-Absender sollte durch eine echte Adresse der Firma ersetzt werden. | +| `MAIL_HOST` / `MAIL_PORT` / `MAIL_USER` / `MAIL_PASS` | nein | nicht gesetzt | Alte Rückfallnamen für den SMTP-Versand, die die API im Code weiterhin liest. Sie haben dort Vorrang vor den gleichnamigen `TESSERA_SMTP_*`-Werten (`MAIL_HOST` vor `TESSERA_SMTP_HOST` usw.; ohne beides greift `localhost:1025`, der Mailhog der Entwicklung). Weder die Compose-Dateien noch `.env.prod.example` reichen diese Namen an den `api`-Container durch; sie wirken also nur, wenn jemand sie von Hand in die Compose-Datei einträgt. Nutzen Sie sie nicht für neue Installationen, sondern `TESSERA_SMTP_*` oder besser die Einstellung in der Oberfläche. | | `TESSERA_BUGREPORT_TO` | nein | leer | Rückfall-Postfach für den Knopf „Fehler melden“ in der Kopfleiste, falls unter Administration → E-Mail-Versand (SMTP) kein Feld „Fehlermeldungen an“ gesetzt ist. Leer = nur die Einstellung in der Oberfläche gilt. Wie `IMAGE_TAG` (Kapitel 9): die Serverdatei `/opt/tessera/docker-compose.prod.yml` bekommt die Zeile `TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}` nur von Hand. | | `APP_URL` | empfohlen | `http://localhost:3001` (für `NEXT_PUBLIC_API_URL`) / `http://localhost:3000` (für `TESSERA_APP_URL`) | Öffentliche Basis-URL der Web-Oberfläche. Wird serverseitig u. a. für in E-Mails generierte Links verwendet (`TESSERA_APP_URL`). | | `API_INTERNAL_URL` | fest verdrahtet | `http://api:3001` | Adresse, unter der `web` die API **innerhalb** des Docker-Netzes erreicht; dorthin schreibt Next.js die `/api-proxy/*`-Rewrites um. In der Regel nicht ändern. | +| `CORS_ORIGIN` | nein | `http://localhost:3000` (im Code der API, in keiner Compose-Datei gesetzt) | Erlaubte Herkunftsadresse für Browser-Anfragen direkt an die API (CORS). Im Normalbetrieb ohne Wirkung, weil der Browser die API nie direkt, sondern nur über `web` und `/api-proxy` erreicht (Kapitel 1). Nur nötig, wenn jemand die API bewusst unter einer eigenen Adresse im Browser aufruft; dann muss die Variable zusätzlich in die Compose-Datei beim Dienst `api` eingetragen werden. | +| `APP_VERSION` / `APP_CHANNEL` / `APP_COMMIT` / `APP_BUILD_TIME` | nein – nicht per `.env` setzen | im Abbild fest eingebaut | Versionsstempel von API (und Web). Sie werden beim Bau des Abbilds durch die Pipeline (`.gitea/scripts/publish-images.sh`) als Build-Argumente übergeben und im Abbild als Umgebungsvariablen abgelegt. Aus ihnen speist sich `/health/version` (Kapitel 9) und die Startzeile im Protokoll. Ein selbst gebautes Abbild ohne diese Angaben meldet als Version und Kanal `dev` und lässt Kennung und Bauzeit leer. | +| `DASHBOARD_IMAGES_DIR` / `FAVORITE_ICONS_DIR` | nein – im Betrieb nie setzen | nicht gesetzt (Ablage unter `/app/user-files/dashboard-images` bzw. `/app/user-files/favorite-icons`) | Schalter, die die Ablage der Bilderrahmen-Bilder bzw. der Favoriten-Symbole auf ein anderes Verzeichnis umlenken. Sie existieren für Tests. Wer sie dennoch setzt, muss dafür sorgen, dass das neue Verzeichnis ebenfalls in einem Volume liegt und in die Sicherung aufgenommen wird (Kapitel 6), sonst gehen die Dateien beim Neuerstellen des Containers verloren. | | `IMAGE_TAG` | empfohlen | `beta` | Welcher Kanal auf diesem Server läuft: `beta` (alle Neuerungen, alpha) oder `live` (nur freigegebene Versionen, tessera.ctl.de). Siehe Kapitel 9. | Hinweis zu `NEXT_PUBLIC_API_URL`: Diese Variable wird beim Image-Build bereits fest @@ -172,7 +185,7 @@ Compose gesetzte Wert (`${APP_URL:-http://localhost:3001}`) hat auf das bereits gebaute Frontend-Bundle daher keine sichtbare Wirkung mehr. Praktisch reicht es, dass `APP_URL` für `TESSERA_APP_URL` (serverseitig, z. B. Mail-Links) korrekt gesetzt ist. - + **Wichtig – Konfigurationsdrift auf dem Server:** Auf dem Produktivserver wurde die laufende Compose-Datei in der Vergangenheit direkt von Hand angepasst und ist damit @@ -202,7 +215,7 @@ Das Modul „Zertifikatsmanager“ braucht keine Einstellungen und keine eigene - **Größe der Anfragen:** Die Dateien gehen über `/api-proxy` an die API. Eine einzelne Analyse schickt höchstens 10 MB (höchstens 30 Dateien, je Datei bis 5 MB). Die Voraussetzung am Proxy ist dieselbe wie im Abschnitt „Dateien (Nextcloud)“: `client_max_body_size` von mindestens `10m`. Beim Herunterladen eines Ergebnisses schickt der Browser nur Zertifikate und höchstens einen Schlüssel als JSON, höchstens 512 KiB je Anfrage (Tessera legt für genau diese Anfrage eine eigene Grenze fest, alle anderen JSON-Anfragen bleiben bei 100 kB). - **Ausgehender Zugriff:** Der Zertifikatsmanager ruft von sich aus nichts im Internet ab. Eine Ausnahme gibt es: Klickt ein Benutzer auf „Fehlendes Zertifikat holen“, schickt der `api`-Container eine einzelne Anfrage an die Adresse, die im Zertifikat als Aussteller-Adresse steht (meist `http://`, selten `https://`). Dafür muss der `api`-Container ausgehend per HTTP und HTTPS (Ports 80 und 443) ins Internet kommen; andere Ports ruft Tessera nie ab. Ist das ausgehend gesperrt (Firewall, Proxy-Pflicht), meldet der Knopf „nicht erreichbar“, alles andere im Modul funktioniert weiter, und die Benutzer laden das Zertifikat selbst herunter. Tessera ruft dabei nur öffentliche Adressen ab (keine internen Rechner, keine Adressen des eigenen Netzes), prüft die Adresse im Moment des Verbindens noch einmal, folgt höchstens drei Weiterleitungen, begrenzt Wartezeit (8 Sekunden) und Antwortgröße (256 KiB) und übernimmt nur ein Zertifikat, das das betroffene Zertifikat wirklich ausgestellt hat. Im Protokoll steht bei einem Fehler genau eine Zeile mit dem Servernamen und einem Fehlercode, nie ein Zertifikat. -- **Rechenaufwand:** Passwortgeschützte PFX-Dateien und verschlüsselte Schlüssel öffnet Tessera mit den eingegebenen Passwörtern (je Datei höchstens zehn verschiedene Versuche); das kostet kurz Rechenzeit, belastet den Server aber nicht dauerhaft. Damit eine präparierte Datei die API nicht ausbremsen kann, gelten Grenzen je Anfrage: eine Passwortableitung höchstens eine Million Runden, alle zusammen höchstens sechs Millionen (mehr überspringt Tessera mit Hinweis), höchstens 200 Zertifikate, 50 Schlüssel und 50 Zertifikatsanfragen sowie 20 MiB für alle Dateien zusammen (Tessera bricht schon beim Empfang ab, mit der Meldung zu große Dateien, HTTP 413). ZIP-Dateien werden mit hartem Deckel entpackt (1 MiB je Eintrag, 20 MiB zusammen). +- **Rechenaufwand:** Passwortgeschützte PFX-Dateien und verschlüsselte Schlüssel öffnet Tessera mit den eingegebenen Passwörtern (je Datei höchstens zehn verschiedene Versuche); das kostet kurz Rechenzeit, belastet den Server aber nicht dauerhaft. Damit eine präparierte Datei die API nicht ausbremsen kann, gelten Grenzen je Anfrage: eine Passwortableitung höchstens eine Million Runden, alle zusammen höchstens sechs Millionen (mehr überspringt Tessera mit Hinweis), höchstens 200 Zertifikate, 50 Schlüssel und 50 Zertifikatsanfragen sowie 20 MiB für alle Dateien zusammen (Tessera bricht schon beim Empfang ab, mit der Meldung zu große Dateien, HTTP 413). ZIP-Dateien werden mit hartem Deckel entpackt (1 MiB je Eintrag, 20 MiB zusammen, höchstens 100 Einträge je ZIP und ein Kompressionsverhältnis von höchstens 100 : 1 je Eintrag); ein ZIP mit mehr Einträgen wird ganz abgelehnt, ein Eintrag mit auffälligem Verhältnis wird als verdächtig übersprungen und im Ergebnis gemeldet. Ordner, `__MACOSX`, versteckte Dateien (Name mit Punkt am Anfang), `Thumbs.db` und `desktop.ini` zählen nicht mit. ## 4. Neue Fassung einspielen @@ -275,16 +288,22 @@ durchgespielt.) Ein separater Migrationsschritt ist **nicht** nötig. Der `api`-Container führt Prisma-Migrationen bei jedem Start automatisch aus, bevor der eigentliche Prozess -hochfährt (`apps/api/Dockerfile`, `CMD`): +hochfährt. Der Startbefehl des Images (`apps/api/Dockerfile`, `CMD`) ruft dafür das +Skript `apps/api/scripts/migrate-and-start.sh` auf. Es arbeitet in drei Schritten: -``` -prisma migrate deploy --schema apps/api/prisma/schema.prisma && node apps/api/dist/main.js -``` +1. Es prüft, dass `DATABASE_URL` gesetzt ist. Fehlt sie, bricht der Container sofort + mit der Meldung „FEHLER: DATABASE_URL ist nicht gesetzt.“ ab. +2. Es führt `prisma migrate deploy --schema apps/api/prisma/schema.prisma` aus. Als + Verbindung dient `TESSERA_MIGRATE_DATABASE_URL`, falls diese Variable gesetzt ist + (Kapitel 3); ist sie leer oder fehlt sie, nimmt das Skript `DATABASE_URL`. +3. Erst wenn die Migration erfolgreich war, startet es die API mit + `exec node apps/api/dist/main.js`. Die API selbst arbeitet immer mit + `DATABASE_URL`. Das heisst: Sobald ein neues Image (mit neuen Migrationen im Ordner `apps/api/prisma/migrations/`) per `pull` + `--force-recreate` eingespielt wird, laufen ausstehende Migrationen beim nächsten Start automatisch. Schlägt eine -Migration fehl, startet `node apps/api/dist/main.js` erst gar nicht – der Container +Migration fehl, startet die API (Schritt 3) erst gar nicht – der Container bleibt dann im Neustart-Loop bzw. terminiert, sichtbar in `docker compose logs api`. Kontrolle, ob eine Migration tatsächlich angekommen ist: @@ -393,6 +412,7 @@ startet `web`, weil `depends_on: api: condition: service_healthy` das erzwingt. |---|---|---| | `docker compose up` bricht sofort ab, ohne dass ein Container startet, mit einer Meldung zu `TESSERA_ENCRYPTION_KEY` | Variable fehlt in `.env` – Compose selbst verweigert die Variablen-Interpolation (`${TESSERA_ENCRYPTION_KEY:?...}`) | `TESSERA_ENCRYPTION_KEY` setzen (siehe Kapitel 2), danach erneut starten. | | `api`-Container startet und beendet sich sofort wieder, Log zeigt `TESSERA_ENCRYPTION_KEY is not set` oder `must be a 64-character hex string` | Key ist zwar irgendwo gesetzt, aber leer oder falsch lang | Mit `openssl rand -hex 32` neu erzeugen, Länge prüfen (64 Zeichen). | +| `api`-Container beendet sich sofort, Log zeigt `FEHLER: DATABASE_URL ist nicht gesetzt.` | `DATABASE_URL` fehlt oder ist leer; das Startskript `apps/api/scripts/migrate-and-start.sh` bricht ab, bevor Prisma sich verbindet (Kapitel 5) | `DATABASE_URL` in der `.env` setzen (Form siehe Kapitel 3) und den `api`-Container neu erstellen (`--force-recreate api`). | | `api` wird nie `healthy`, Log zeigt Verbindungsfehler von Prisma/PostgreSQL | `db` ist noch nicht bereit, `DATABASE_URL` falsch, oder `DB_PASSWORD`/`DATABASE_URL` passen nicht zusammen | `docker compose ps` – ist `db` `healthy`? `DATABASE_URL` gegen `DB_PASSWORD` abgleichen. | | Login funktioniert nicht, obwohl `api` und `web` laufen | `JWT_SECRET` bei `web` und `api` unterschiedlich, z. B. nach halbherzigem Neustart nur eines Containers | Beide Container mit demselben `JWT_SECRET` neu erstellen (`--force-recreate api web`). | | Neue Version scheint nicht anzukommen, obwohl `pull` gelaufen ist | Klassische `up -d`-Falle ohne `--force-recreate` (siehe Kapitel 4) | `StartedAt` des Containers gegen `Created` des Images vergleichen, ggf. `--force-recreate` nachholen. | @@ -582,6 +602,11 @@ Vier Wege, vom einfachsten zum genauesten: Die Antwort enthält die Felder `version`, `channel` (`beta` oder `live`), `commit` (Kurzkennung) und `buildTime` (wann das Image gebaut wurde). + Diese Angaben stammen aus den Umgebungsvariablen `APP_VERSION`, `APP_CHANNEL`, + `APP_COMMIT` und `APP_BUILD_TIME`, die die Pipeline beim Bau des Images fest + einträgt (`.gitea/scripts/publish-images.sh`); sie werden nicht über die `.env` + gesetzt. Ein Image, das jemand von Hand ohne diese Angaben gebaut hat, meldet + deshalb `dev` als Version und Kanal und leere Felder für Kennung und Bauzeit. 3. **Im Protokoll:** ```bash diff --git a/docs/anleitung-entwicklung.md b/docs/anleitung-entwicklung.md index 4b9c28a..157dd28 100644 --- a/docs/anleitung-entwicklung.md +++ b/docs/anleitung-entwicklung.md @@ -138,6 +138,21 @@ Das startet: `docker compose up` ohne `--build`/`--force-recreate` baut bestehende Images **nicht** neu — nach Änderungen an Dockerfiles oder Dependencies muss `--build` explizit mitgegeben werden. +Die API läuft im Dev-Stack im Watch-Modus (`nest start --watch`). Dasselbe Skript gibt es für den +Betrieb außerhalb von Compose als `pnpm --filter @tessera/api start:dev`; `pnpm --filter +@tessera/api start` startet dagegen den fertig gebauten Stand (`node dist/main.js`). Im +produktiven Image startet die API nicht direkt, sondern über `apps/api/scripts/migrate-and-start.sh` +(siehe [Datenbank und Migrationen](#datenbank-und-migrationen)). + +Neben den beiden Dateien für die Entwicklung liegen zwei weitere Compose-Dateien im Repository, die +Sie lokal nicht brauchen: + +- `docker-compose.prod.yml` — der produktive Stack. Er zieht die fertigen Abbilder `web`, `api` + und `db` aus der Registry; der Kanal (`beta` oder `live`) kommt aus `IMAGE_TAG` in der `.env` + des jeweiligen Servers (Vorgabe `beta`). +- `docker-compose.ci.yml` — beschreibt den Gitea-Runner (`act_runner`), der die CI-Aufträge in + Containern ausführt. Details dazu stehen in `docs/ci-cd-setup.md`. + `web`, `api`, `db` und `mailhog` tragen `restart: unless-stopped`: Nach einem Neustart des Rechners kommen sie von selbst wieder hoch, sofern sie vorher liefen. Mit `docker compose stop` angehaltene Container bleiben aus. @@ -224,7 +239,7 @@ vorgesehen. Sprache, Symbol und Bilder des Installers stehen in sich nur über den CI-Bau auf einem Windows-Rechner prüfen, lokal validiert `cargo check` lediglich die Schlüssel. -**Im CI wird die Desktop-App nur gebaut, wenn sich etwas an ihr geändert hat.** Der Job `desktop` vergleicht einen Stempel aus Versionsnummer und letztem Commit an `apps/desktop/`, `desktop-version.sh`, `desktop-collect.sh`, `desktop-stamp.sh` und `ci.yml` mit dem Zwischenspeicher des Runners und übernimmt bei Treffer die zuletzt gebauten Pakete (Details: `docs/ci-cd-setup.md`, Abschnitt 4). Eine Änderung außerhalb dieser Pfade – etwa nur in `pnpm-lock.yaml` – löst keinen Desktop-Bau aus; soll trotzdem neu gebaut werden, genügt eine Änderung unter `apps/desktop/`. Stempel lokal ansehen: +**Im CI wird die Desktop-App nur gebaut, wenn sich etwas an ihr geändert hat.** Der Job `desktop` vergleicht einen Stempel aus Versionsnummer und letztem Commit an `apps/desktop/`, `desktop-version.sh`, `desktop-collect.sh`, `desktop-stamp.sh`, `appimage-strip-wayland.sh` und `ci.yml` mit dem Zwischenspeicher des Runners und übernimmt bei Treffer die zuletzt gebauten Pakete (Details: `docs/ci-cd-setup.md`, Abschnitt 4). Eine Änderung außerhalb dieser Pfade – etwa nur in `pnpm-lock.yaml` – löst keinen Desktop-Bau aus; soll trotzdem neu gebaut werden, genügt eine Änderung unter `apps/desktop/`. Stempel lokal ansehen: ```bash DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh stamp @@ -236,20 +251,39 @@ DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh ``` (auth)/ — /login, /reset-password — öffentliche Routen -(portal)/ — alles hinter Login: /admin, /marketplace, /modules, /settings, /change-password +(portal)/ — alles hinter Login: /admin, /changelog, /marketplace, /modules, /settings, + /change-password ``` Die Route-Groups `(auth)` und `(portal)` teilen sich kein gemeinsames Layout im URL-Pfad, tragen aber unterschiedliche Layout-Bäume. Innerhalb von `(portal)` liegt `modules/[category]/[moduleSlug]` -als generische Route für beliebige Module sowie vier fest verdrahtete Modulverzeichnisse -(`cert-manager`, `dkv-fleet`, `domaincheck`, `tender-radar`) mit eigenen `layout.tsx`-Dateien — -Details dazu im Abschnitt [Das Modulsystem](#das-modulsystem). +als generische Route für beliebige Module sowie zehn fest verdrahtete Modulverzeichnisse +(`cert-manager`, `dkv-fleet`, `domaincheck`, `domains`, `handelsware-datev`, `kantine-datev`, +`nextcloud-files`, `nextcloud-status`, `proxmox`, `tender-radar`) mit eigenen `layout.tsx`-Dateien. +Dazu kommt `modules/custom/[id]` für die „Eigenen Module“ (ohne Gate-Layout, siehe +[Eigene Module](#eigene-module)) — Details im Abschnitt [Das Modulsystem](#das-modulsystem). -**Backend** (`apps/api/src`, NestJS): ein Modul pro fachlicher Domäne -(`auth`, `user`, `tenant`, `groups`, `module-registry`, `domaincheck`, `dkv`, `cert-manager`, -`tenders`, `calendar`, `dashboard`, `favorites`, `settings`, `ldap`, `mail`, `crypto`, `health`, -`prisma`). Jedes Domänen-Modul folgt dem NestJS-Muster `*.module.ts` / `*.controller.ts` / -`*.service.ts`. +Weitere Bereiche hinter dem Login: + +- `/changelog` — die Seite „Was ist neu“ (siehe [Konventionen und Fallstricke](#konventionen-und-fallstricke)). +- `/admin` — der Bereich „Administration“ (Benutzer, Gruppen, Module, Verzeichnisanbindung). + Unterbereiche: `users`, `groups`, `modules` + (mit `grants` für die Freigaben und `categories` für die Modulkategorien), `custom-modules`, + `ldap`, `smtp`, `welcome-mail` und `tenants`. +- `/settings` — persönliche Einstellungen mit `general` (Konto, Desktop), `dashboard` und + `custom-modules`. + +**Backend** (`apps/api/src`, NestJS): ein Modul pro fachlicher Domäne. Die Plattform-Bausteine sind +`auth`, `user`, `tenant`, `groups`, `module-registry`, `module-categories`, `custom-modules`, +`dashboard`, `favorites`, `settings`, `reminders`, `bug-reports`, `desktop`, `ldap`, `mail`, +`inbox` (gemeinsame IMAP-/Exchange-Postfachanbindung, die mehrere Fachmodule nutzen), `crypto`, +`health` und `prisma`. Die Fachmodule sind `domaincheck`, `domains`, `dkv`, `cert-manager`, +`tenders`, `calendar`, `proxmox`, `nextcloud-files`, `nextcloud-status`, `handelsware-datev` und +`kantine-datev`; `accounting` enthält Hilfsfunktionen (CSV- und Dateinamen-Dekodierung), die +Handelsware und Kantinenabrechnung gemeinsam nutzen. `common` ist ein reiner Hilfsordner, kein +Fachmodul. Jedes Domänen-Modul folgt dem NestJS-Muster `*.module.ts` / `*.controller.ts` / +`*.service.ts`. Der `desktop`-Controller liefert die Desktop-Pakete aus (`GET /desktop/latest`, +`GET /desktop/update`, `GET /desktop/download/:platform`). **Weg einer Anfrage** (Beispiel: eine Modulseite lädt Daten): @@ -271,8 +305,10 @@ Details dazu im Abschnitt [Das Modulsystem](#das-modulsystem). ## Das Modulsystem Module sind das zentrale Organisationsprinzip von Tessera: fachliche Werkzeuge (Domaincheck, -Zertifikatsmanager, DKV-Rechnung, Ausschreibungs-Radar), die im Marktplatz erscheinen, pro Mandant -aktiviert und dann einzelnen Gruppen oder Benutzern freigegeben werden. +Domains, Zertifikatsmanager, DKV-Rechnung, Ausschreibungs-Radar, Proxmox, Dateien, Nextcloud-Status, +Handelsware und Kantinenabrechnung), die im Marktplatz erscheinen, pro +Mandant aktiviert und dann einzelnen Gruppen oder Benutzern freigegeben werden. Daneben gibt es die +[Eigenen Module](#eigene-module), die ohne Aktivierung und Freigabe auskommen. ### Registrierung @@ -369,17 +405,26 @@ Zugriff auf ein Modul besteht aus zwei unabhängigen Stufen: SUPER_ADMIN). Ohne Aktivierung ist das Modul für niemanden im Mandanten erreichbar, auch nicht über einen Grant. 2. **Grant pro Gruppe oder Benutzer** (`ModuleGrant`) — erst wenn das Modul aktiv ist, entscheidet - ein Grant, wer es tatsächlich sieht. `ModuleGrant` trägt bewusst kein Rechtestufen-Feld, nur - An/Aus (D-04 im Code-Kommentar des Schemas), und ist Gruppe **oder** Benutzer, nie beides. + ein Grant, wer es tatsächlich sieht. `ModuleGrant` trägt + eine Freigabestufe, das Feld `level` (Enum `ModuleGrantLevel`, Vorgabe `USE`): `USE` heißt + Benutzen, `MANAGE` heißt Benutzen und zusätzlich die Einstellungen des Moduls ändern. Freigaben + erteilen bleibt Administratoren vorbehalten. Ein Grant gilt für eine Gruppe **oder** einen + Benutzer, nie beides (D-04). -Beide Stufen werden ausschließlich von einer einzigen Funktion aufgelöst: -`ModuleAccessService.getAccessibleModuleIds(tenantId, userId, role)` -(`apps/api/src/module-registry/module-access.service.ts`). ADMIN und SUPER_ADMIN umgehen die -Grant-Prüfung und bekommen automatisch alle mandantenweit aktiven Module. Für die Rolle USER ist es -die Vereinigungsmenge aus Direkt-Grants und Grants über Gruppenmitgliedschaft, geschnitten mit den -aktiven Modulen des Mandanten. Diese eine Funktion versorgt drei Stellen — den `ModuleGuard` im +Beide Stufen werden ausschließlich an einer Stelle aufgelöst: +`ModuleAccessService.getModuleAccessLevels(tenantId, userId, role)` +(`apps/api/src/module-registry/module-access.service.ts`). Die Funktion liefert je zugänglichem Modul die +Stufe. ADMIN und SUPER_ADMIN umgehen die Grant-Prüfung und bekommen automatisch alle +mandantenweit aktiven Module mit der Stufe `MANAGE`. Für die Rolle USER ist es die +Vereinigungsmenge aus Direkt-Grants und Grants über Gruppenmitgliedschaft, geschnitten mit den +aktiven Modulen des Mandanten; besteht der Zugriff über mehrere Wege, gewinnt die höhere Stufe. +`getAccessibleModuleIds(tenantId, userId, role)` ist nur die Schlüsselmenge davon — es liefert +also die Modul-IDs ohne Stufe. Diese eine Auflösung versorgt drei Stellen — den `ModuleGuard` im Backend, `GET /modules/active` (Sidebar) und `GET /modules/catalog` (Marktplatz) — damit keine -dieser Stellen unabhängig voneinander driften kann. +dieser Stellen unabhängig voneinander driften kann. Der Dekorator `@UseModule(slug)` verlangt +Zugriff auf das Modul; `@ModuleManage(slug)` (ebenfalls in `module.guard.ts`) verlangt zusätzlich +die Stufe `MANAGE` und ersetzt `@Roles(ADMIN, SUPER_ADMIN)` bei Routen, die nur dieses eine Modul +konfigurieren. Beide nie zusammen mit `@Roles` am selben Handler einsetzen. ### Vom Backend-Endpunkt zur Seite im Portal @@ -400,9 +445,13 @@ Im Frontend gibt es zwei Wege, wie eine Modulseite unter `/modules/...` erreichb - **Generische Route** `apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx` — für jedes Modul über `[category]`/`[moduleSlug]` erreichbar. -- **Vier fest verdrahtete Modulverzeichnisse**: `modules/cert-manager`, `modules/dkv-fleet`, - `modules/domaincheck`, `modules/tender-radar` — mit eigenen Unterrouten (z. B. - `dkv-fleet/vehicles`, `tender-radar/my-sources`, `*/settings`). +- **Zehn fest verdrahtete Modulverzeichnisse**: `modules/cert-manager`, `modules/dkv-fleet`, + `modules/domaincheck`, `modules/domains`, `modules/handelsware-datev`, `modules/kantine-datev`, + `modules/nextcloud-files`, `modules/nextcloud-status`, `modules/proxmox` und + `modules/tender-radar` — mit eigenen Unterrouten (z. B. `dkv-fleet/vehicles`, + `tender-radar/my-sources`, `*/settings`). Jedes davon hat zugleich einen Eintrag in + `MODULE_REGISTRY`. Daneben gibt es `modules/custom` — die bewusste Ausnahme ohne Gate-Layout + für die [Eigenen Module](#eigene-module). Beide Wege rendern denselben Baustein: die Server-Komponente `ModuleAccessGate` (`apps/web/src/components/modules/module-access-gate.tsx`). Sie ruft `checkModuleAccess(moduleSlug)` @@ -412,11 +461,12 @@ auf, was `GET /modules/active` mit dem Session-Cookie anfragt — dieselbe einen Fehler) zeigt eine gemeinsame 403-Ansicht. **Bekannter Fallstrick (behoben, aber lehrreich):** Ursprünglich saß dieser Zugriffs-Check nur in -der generischen `[category]/[moduleSlug]`-Route. Die vier fest verdrahteten Modulverzeichnisse -hatten **keinen eigenen** `ModuleAccessGate` und liefen an der Prüfung vorbei — ein direkter Aufruf +der generischen `[category]/[moduleSlug]`-Route. Die damals vier fest verdrahteten Modulverzeichnisse +(`cert-manager`, `dkv-fleet`, `domaincheck`, `tender-radar`) hatten **keinen eigenen** `ModuleAccessGate` und liefen an der Prüfung vorbei — ein direkter Aufruf von z. B. `/modules/dkv-fleet` umging die Freigabeprüfung vollständig, obwohl die generische Route -korrekt geschützt war. Der Fix (Commit `74a30fb`/`5504931`) gibt jedem der vier Modulverzeichnisse -ein eigenes `layout.tsx`, das denselben `ModuleAccessGate` einbindet: +korrekt geschützt war. Der Fix (Commit `74a30fb`/`5504931`) gab jedem dieser Verzeichnisse +ein eigenes `layout.tsx`, das denselben `ModuleAccessGate` einbindet; heute tragen es alle zehn +fest verdrahteten Modulverzeichnisse: ```ts // apps/web/src/app/(portal)/modules/dkv-fleet/layout.tsx @@ -429,7 +479,13 @@ export default function DkvFleetLayout({ children }: { children: ReactNode }) { braucht **immer** ein eigenes `layout.tsx` mit `ModuleAccessGate`, genau wie sein Backend-Controller `@UseModule('')` braucht. Beide Prüfungen sind unabhängig voneinander — die eine ersetzt nicht die andere; das Frontend-Gate ist Komfort/UX (keine leere Seite ohne Erklärung), das Backend-Gate ist -die tatsächliche Zugriffskontrolle. +die tatsächliche Zugriffskontrolle. Den Wächter dafür stellt +`apps/web/src/app/(portal)/modules/module-layouts.test.tsx`: Er prüft für die dort aufgeführten Layouts, dass sie das +`ModuleAccessGate` mit genau dem Slug ihres Ordnernamens einbinden (`proxmox` fehlt in dieser Liste +bisher), und dass **jedes** +Modulverzeichnis (außer den dynamischen `[...]`-Ordnern und dem bewusst ausgenommenen `custom`) +ein `layout.tsx` besitzt. Wer ein neues Modulverzeichnis ohne Layout anlegt, bekommt einen roten +Test; wer ein Layout anlegt, trägt es dort zusätzlich in die Slug-Tabelle ein. Zusätzlich läuft im Frontend eine dritte, unabhängige Absicherung: `ModuleShell` (`apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-shell.tsx`) lädt die @@ -439,7 +495,7 @@ ein beliebiger Slug aus der URL löst sonst keinen Import aus. ### So entsteht ein neues Modul — Walkthrough am Beispiel Domaincheck -Domaincheck ist das kleinste vorhandene Modul und eignet sich als Vorlage. Die realen Dateien: +Domaincheck ist ein kleines, überschaubares Modul und eignet sich als Vorlage. Die realen Dateien: **Backend** (`apps/api/src/domaincheck/`): @@ -448,7 +504,8 @@ Domaincheck ist das kleinste vorhandene Modul und eignet sich als Vorlage. Die r 3. `domaincheck.controller.ts` — `@Controller('modules/domaincheck')` mit `@UseModule('domaincheck')` auf Klassenebene, ein `@Post('check')`-Handler. 4. `domaincheck.seed.ts` — die `seedDomaincheckModule()`-Funktion mit dem Manifest (`slug`, `name`, - `version`, `category`, `description`, `isSystem`). + `version`, `category`, `description`, `isSystem`). Die Version kommt aus dem Modul-Changelog, siehe + [Modulversion und Modul-Changelog pflegen](#modulversion-und-modul-changelog-pflegen). 5. `domaincheck.module.ts` — bindet Controller/Service zusammen, importiert `ModuleRegistryModule`, ruft in `onModuleInit()` den Seed auf. 6. Eintrag des neuen Moduls in `apps/api/src/app.module.ts` unter `imports`. @@ -488,10 +545,36 @@ diesem globalen `fetch` ignoriert. Gemessen und dokumentiert in `apps/api/src/proxmox/proxmox-client.service.ts` (zweites, unabhängig davon konstruiertes Auftreten mit demselben Befund). +### Eigene Module + +Neben den eingebauten Modulen gibt es die „Eigenen Module“: Seitenleisten-Einträge, die eine +externe `https`-Seite im Rahmen des Portals anzeigen. Sie sind keine Zeilen der Tabelle `Module`, +hängen deshalb an keiner Mandanten-Aktivierung und keinem Grant und sind für alle angemeldeten +Benutzer sichtbar. Die Teile: + +- **Backend** `apps/api/src/custom-modules/` (`@Controller('custom-modules')`): Es gibt gemeinsame + Einträge (nur Administratoren legen sie an, ändern und löschen sie) und persönliche Einträge + (nur der Besitzer sieht und ändert sie; für fremde Kennungen antwortet die API immer mit 404). Die + Rollenentscheidung trifft der Dienst, weil sie vom Eintrag abhängt, nicht von der Route — deshalb + tragen die Routen weder `@Roles` noch `@UseModule`. Die Kategorien kommen aus + `apps/api/src/module-categories/`. +- **Frontend**: die Rahmenseite `modules/custom/[id]` (bewusst ohne `ModuleAccessGate`; der + statische Ordner `custom` hat im App Router Vorrang vor `[category]/[moduleSlug]`), die + Bausteine `apps/web/src/components/modules/custom-module-*.tsx`, die Pflege der + gemeinsamen Einträge für Administratoren unter `/admin/custom-modules` und die persönlichen + Einträge unter `/settings/custom-modules`. +- **Wächter**: `apps/web/src/messages/module-categories.spec.ts` prüft, dass jede Modulkategorie + einen Anzeigenamen in beiden Sprachen hat; `module-layouts.test.tsx` führt `custom` als einzige + begründete Ausnahme von der Layout-Pflicht. + ### Eine Kachel zum Modul -Ein Modul kann zusätzlich als Kachel auf dem Dashboard erscheinen. Seit -`quick-260922-m1h` sind dafür **drei** Stellen nötig (vorher waren es sieben): +Ein Modul kann zusätzlich als Kachel auf dem Dashboard erscheinen. `WIDGET_TYPES` kennt zwölf +Kachel-Typen: `clock`, `search`, `calendar`, `note`, `calculator`, `favorites`, `stopwatch`, +`picture-frame`, `xframe`, `proxmox`, `reminder` und `nextcloud-status`. Davon sind nur `proxmox` und +`nextcloud-status` an ein Modul gebunden; alle übrigen (auch `reminder`) sind Plattform-Kacheln, die +immer sichtbar sind. Seit `quick-260922-m1h` sind für eine neue Kachel **drei** Stellen nötig +(vorher waren es sieben): 1. **Die Kachel-Komponente schreiben** — `apps/web/src/components/dashboard/widgets/-widget.tsx`, nimmt die `WidgetProps` aus `widget-registry.tsx` (`instanceId`, `config`, `isEditMode`) entgegen. @@ -538,6 +621,14 @@ den Proxmox-Hosts. Und Zeilen, die im Ansichtsmodus Links sind, werden im Bearbe schlichten Elementen ohne Ziel: Links stehen im Abbruch-Selektor von `dashboard-grid.tsx`, eine Kachel aus Links ließe sich sonst kaum noch ziehen. +**Zweites Beispiel: Nextcloud-Status (`quick-261002-k67`).** Die zweite modulgebundene Kachel +folgt demselben Muster: `WIDGET_MODULE_SLUGS` trägt `'nextcloud-status': 'nextcloud-status'`, die +Komponente ist `apps/web/src/components/dashboard/widgets/nextcloud-status-widget.tsx`, und die +Daten kommen über die Endpunkte des Moduls (`/modules/nextcloud-status/*`, im Frontend gebündelt in +`apps/web/src/lib/nextcloud-status-api.ts`, das sich an `proxmox-api.ts` orientiert). Der Katalog- +Test `widget-catalog-modal.test.tsx` prüft, dass beide Modul-Kacheln nur bei Zugriff auf das jeweilige +Modul erscheinen. + ## Mandantentrennung Der tatsächliche Mechanismus ist `TenantGuard` (`apps/api/src/tenant/tenant.guard.ts`), global als @@ -547,8 +638,10 @@ SUPER_ADMIN einen Wechsel per `x-tenant-id`-Header, und setzt anschließend AUSS `req.tenantId` (260911-e2s). Die Bindung an den Mandanten geschieht dienst-intern, je Service-Methode neu, über das Bindungshilfsmittel `forTenant()` (`apps/api/src/prisma/prisma-tenant.extension.ts`), das vor **jeder** Query in einer Transaktion -`SELECT set_config('app.current_tenant', $1, true)` ausführt — der Guard selbst erzeugt keinen -Prisma-Client mehr und veröffentlicht keinen auf dem Anfrageobjekt. +eine einzige `SELECT set_config(...)`-Anweisung ausführt. Sie setzt drei Werte zugleich: +`app.current_tenant` (der Mandant), `app.current_user` (der optionale dritte Parameter `userId`, +leer, wenn keiner übergeben wird) und `app.system_context` (wird dabei auf leer gesetzt). Der Guard +selbst erzeugt keinen Prisma-Client mehr und veröffentlicht keinen auf dem Anfrageobjekt. > Ein früherer Entwurf veröffentlichte zusätzlich einen gebundenen Prisma-Client auf dem > Anfrageobjekt, dupliziert in einer gleichnamigen, nie in `app.module.ts` registrierten @@ -589,6 +682,34 @@ Bei den RLS-geschützten Tabellen greift die DB-seitige Absicherung zusätzlich, läuft tatsächlich über einen dienst-intern per `forTenant()` gebundenen Client und nicht über den globalen, ungebundenen `PrismaService`. +**Systemkontext für Hintergrunddienste:** Ein Dienst, der ohne Anfrage einmal über **alle** Mandanten +lesen muss und danach je Mandant gebunden handelt (zum Beispiel die Hintergrunddienste von DKV, Proxmox, +Nextcloud-Status, Erinnerungs-Mails und Ausschreibungs-Zusammenfassung, der LDAP-Abgleich und das +Ausschreibungs-Matching), nutzt `forSystem()` aus derselben Datei. Es setzt `app.system_context` +auf `'true'` und leert dabei `app.current_tenant` und `app.current_user`. Geöffnet ist nur das +**Lesen** (eine zusätzliche `FOR SELECT`-Regel); jedes Schreiben scheitert weiterhin an der +Mandantenregel. Ein Anfrageweg darf `forSystem()` nie aufrufen — es würde an jeder Mandantenregel +vorbeilesen. Welche Dateien es rufen dürfen, steht mit der genauen Zahl der Aufrufe je Datei in +`FORSYSTEM_ALLOWED_CALL_SITES` in `rls-access-inventory.spec.ts`; jeder weitere Aufruf macht +diesen Test rot. + +**Wächter-Tests für die Mandantentrennung** (`apps/api/src/prisma/`): Sie lesen Schema, +Migrationen und Quelltext und werden rot, sobald etwas auseinanderläuft: + +- `rls-coverage.spec.ts` — misst aus `schema.prisma` und den Migrationen, welche Modelle eine + `tenantId` tragen und ob für sie eine RLS-Regel existiert; bleibt auch für künftige Modelle gültig. +- `rls-access-inventory.spec.ts` — ermittelt alle Zugriffe auf Modelle im Quelltext (gebunden, + ungebunden, gemischt, systemgebunden) und vergleicht sie mit der Bestandsaufnahme in + `docs/mandantentrennung-zugriffsklassifikation.md`. Eine neue Fundstelle ohne Eintrag oder ein + Eintrag ohne Fundstelle lässt den Test fehlschlagen — deshalb gehört die Doku-Zeile in dieselbe + Änderung wie der neue Zugriff. +- `rls-app-role.spec.ts` — prüft die Migration der Datenbankrolle `tessera_app` (ohne Superuser- und + BYPASSRLS-Recht) rein textuell. +- `rls-preflight.spec.ts` — prüft `apps/api/scripts/rls-preflight.mjs` in der Betriebsart + `--print-plan`, also ohne Datenbankverbindung. Das Skript selbst misst gegen eine Datenbankrolle + (Verbindung aus `TESSERA_PREFLIGHT_DATABASE_URL`), ob die Mandantentrennung unter ihr tatsächlich + greift; es liest nur und schreibt nichts. + ## Berechtigungen Rollen kommen aus dem Prisma-`enum Role { SUPER_ADMIN, ADMIN, USER }` und stecken im JWT — nie aus @@ -607,7 +728,7 @@ Dekorators auf Handler oder Controller. Für den Modulzugriff kommt das oben beschriebene Grant-Modell hinzu: `Group` (mandantenintern, optional an ein AD-Objekt über `ldapDn`/`ldapObjectGuid` gebunden), `GroupMembership` (Benutzer-zu-Gruppe, `source: MANUAL | LDAP`) und `ModuleGrant` (Gruppe **oder** Benutzer, XOR, -kein Rechtestufen-Feld). Die Schreibseite dafür ist +mit Freigabestufe `level` = `USE` oder `MANAGE`). Die Schreibseite dafür ist `apps/api/src/groups/module-grants.controller.ts`, ausschließlich für ADMIN/SUPER_ADMIN: | Route | Zweck | @@ -636,12 +757,22 @@ Der Workflow für eine Schemaänderung: (`apps/api/Dockerfile`) setzt als `CMD`: ``` -prisma migrate deploy --schema apps/api/prisma/schema.prisma && node apps/api/dist/main.js +sh apps/api/scripts/migrate-and-start.sh ``` -Ein neu gestarteter `api`-Container wendet also jede noch ausstehende Migration selbst an, bevor -die Anwendung überhaupt hochfährt — es gibt keinen separaten manuellen Migrationsschritt beim -Deployment. +Das Skript führt zuerst `prisma migrate deploy --schema apps/api/prisma/schema.prisma` aus und +startet danach die API (`node apps/api/dist/main.js`, per `exec`, damit Signale wie `SIGTERM` +den Node-Prozess erreichen). Ein neu gestarteter `api`-Container wendet also jede noch ausstehende +Migration selbst an, bevor die Anwendung überhaupt hochfährt — es gibt keinen separaten manuellen +Migrationsschritt beim Deployment. + +Das Skript trennt dabei die Verbindung für die Migration von der Verbindung für den Betrieb: Für +`prisma migrate deploy` gilt `TESSERA_MIGRATE_DATABASE_URL`, falls gesetzt (Rechte des +Tabelleneigentümers für DDL), sonst `DATABASE_URL`; die API selbst läuft immer mit `DATABASE_URL`. +Ohne gesetzte Migrationsverbindung nutzen beide Schritte `DATABASE_URL` — der bisherige Ablauf, +unverändert für lokale Entwicklung und CI. Die Hintergründe zur Rollentrennung stehen in +`docs/mandantentrennung-datenbankrolle.md`; `apps/api/src/prisma/start-script.spec.ts` prüft das +Skript in der Betriebsart `--print-plan` ohne Datenbank. RLS-Policies werden nicht von Prisma selbst verwaltet, sondern als reines SQL innerhalb regulärer Migrationsdateien mitgeliefert (`CREATE POLICY ...` in `migration.sql`) — siehe @@ -669,6 +800,19 @@ gegen Services, Controller-Logik und React-Komponenten. Guard-artige Spezifikati gegen bereits einmal aufgetretene Fehler — wiederkehrende Fallstricke werden in diesem Projekt durch einen Test abgesichert, nicht nur durch einen Kommentar. +**Wächter-Tests (Regressionsschutz):** Mehrere Tests sind keine Funktionstests, sondern halten eine +Regel fest, die einmal verletzt wurde. Die wichtigsten: + +| Test | Was er absichert | +|---|---| +| `apps/web/src/app/(portal)/modules/module-layouts.test.tsx` | Jedes Modulverzeichnis hat ein `layout.tsx` mit `ModuleAccessGate` (Ausnahme: `custom` und dynamische `[...]`-Ordner) | +| `apps/web/src/messages/umlaut-guard.spec.ts` | Keine Ersatzschreibung („fuer“, „loeschen“) in `de.json`/`en.json` | +| `apps/web/src/messages/tenderRadar-parity.spec.ts` | `de.json` und `en.json` haben im Namensraum `tenderRadar` dieselben Schlüssel | +| `apps/web/src/messages/module-categories.spec.ts` | Jede Modulkategorie hat einen Anzeigenamen in beiden Sprachen | +| `apps/web/src/components/dashboard/widget-registry.test.tsx` | `WIDGET_TYPES`, Registry und Größenvorgaben der Kacheln sind deckungsgleich | +| `apps/api/src/module-registry/module-changelog.spec.ts` | Jedes Seed-Modul hat einen Changelog, die Version stimmt mit dem obersten Eintrag | +| `apps/api/src/prisma/rls-*.spec.ts` | Mandantentrennung: Abdeckung, Zugriffsinventar, Datenbankrolle, Preflight (siehe [Mandantentrennung](#mandantentrennung)) | + **Desktop-Modul (`apps/api/src/desktop`):** ```bash