Commit Graph
31 Commits
Author SHA1 Message Date
alientimandClaude Sonnet 5 0f0fa99e53 Feld-/Funktionserklaerungen als Hover-Tooltip + Klick-Modal (v1.1.10)
Statisch sichtbare Erklaerungstexte (.hint/.field-hint) app-weit auf ein
einheitliches "i"-Icon umgestellt: Hover zeigt den Text neben dem
Mauszeiger, Klick/Tap oeffnet ein wiederverwendetes Modal mit dem vollen
Text. Zentral in app.js (initHintIcons, ensureHintTooltip/-Modal) via
Event-Delegation, kein Aufwand pro Seite. Hover ist bewusst per
matchMedia("(hover: hover) and (pointer: fine)") gated -- auf Touch-
Geraeten faellt automatisch nur Tap-zu-Modal an, da echte
mouseenter/mousemove-Events dort ohnehin nicht zuverlaessig auftreten.

Nicht jede .hint/.field-hint wurde umgestellt -- nach folgenden Kriterien
entschieden:
- Live-Status-Anzeigen (aktuelle Log-Pfade, Netzwerkstatus, Auditlog-
  Schwellenwerte, aktive Reservierungs-/Lease-Zaehler, certbot-Timer-
  Status usw.) bleiben sichtbar, da sie aktuellen Zustand zeigen, keine
  generische Erklaerung.
- Kurze, einzeilige Abschnitts-Untertitel (z.B. "Server, Bind-Konto und
  Suchparameter fuer die Anbindung an AD/LDAP.") bleiben als Orientierung
  direkt unter der Ueberschrift sichtbar.
- Bereits JS-live-aktualisierte Status-Felder (id-tragend, z.B.
  "Fragt live... ab." -> "N Gruppen geladen.") bleiben unangetastet.
- Laengere, mehrsaetzige Abschnitts-Erklaerungen (z.B. AD-Gruppenzu-
  ordnungen, Fileshare-Gruppenzuordnung, Subnetze, DHCP-Options, Wartung)
  sowie so gut wie alle Feld-Erklaerungen wandern ins Icon.

Nebenbei: Karte "Fileshare-Gruppen" in LDAP-Einstellungen zu "Fileshare-
Gruppenzuordnung" umbenannt (passt jetzt zur bereits bestehenden
log_action-Zielbezeichnung in app.py).

Neue gemeinsame Macro-Datei templates/_hint_icon.html, importiert von
15 Templates. Verifiziert: alle 18 betroffenen Seiten laden fehlerfrei
(200, keine JS-Fehler), Icon-Anzahl je Seite geprueft, Hover-Tooltip und
Klick-Modal auf Desktop, Tap-Modal auf simuliertem Mobilgeraet (390x844,
Touch), sowie stichprobenartig bestaetigt dass die bewusst belassenen
Hinweise unveraendert sichtbar bleiben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 22:07:33 +02:00
alientimandClaude Sonnet 5 9b4cf8c2e1 Auditlog: Statistik-Kacheln zeigen geladen/gesamt (weiterhin v1.1.9)
Alle vier Kacheln (Alle, Hinzufuegen, Aenderungen, Loeschungen) zeigen
jetzt "geladen / gesamt" (z.B. "300 / 466"), solange noch nicht alles
geladen ist -- Gesamtzahlen je Kategorie kommen aus einer einzigen
aggregierten SQL-Abfrage (SUM/CASE ueber die bestehenden LIKE-Muster von
category_of), nicht aus einem vollstaendigen Tabellen-Scan. Sobald alles
geladen ist, wird wieder nur die reine Zahl angezeigt (client- wie
serverseitig identische Formatierungslogik).

Dieser Fix wird per Nutzervorgabe unter der GLEICHEN Releasnummer v1.1.9
ausgeliefert (Tag wird bewusst weitergezogen statt neu vergeben, da
v1.1.9 zum Zeitpunkt dieses Commits zwar schon getaggt, aber die
Kachel-Anzeige noch nicht ausgereift war).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 21:38:30 +02:00
alientimandClaude Sonnet 5 1c0082d3d4 Auditlog: Nachladen aelterer Eintraege in 300er-Schritten (v1.1.9)
Die Auditlog-Seite zeigte bisher unveraenderlich nur die neuesten 500
Zeilen an -- alles Aeltere war komplett unsichtbar (auch wenn es noch
nicht archiviert war, siehe v1.1.6). Jetzt:

- Seitengroesse von 500 auf 300 reduziert (Erstladung UND jeder Nachlade-
  Schritt).
- Neuer Button "Mehr laden (300)" (orange) laedt per AJAX genau den
  naechsten 300er-Block aelterer Eintraege nach (Cursor-Pagination ueber
  die id-Spalte) und haengt ihn ans bestehende tbody an, ohne die Seite
  neu zu laden.
- Neuer Button "Alle laden" ruft denselben Nachlade-Schritt wiederholt
  auf, bis wirklich alles geladen ist (mit Fortschrittsanzeige im
  Button-Text), statt in einer Anfrage die komplette (potenziell sehr
  grosse) Tabelle auf einmal zu rendern.
- Suche/Sortierung/Kategorie-Filter sowie die vier Statistik-Kacheln
  arbeiten transparent ueber ALLE aktuell geladenen Zeilen (nicht nur die
  urspruenglichen 300) -- Zeilenliste fuer die Filterung wird jetzt bei
  jedem Aufruf frisch aus dem DOM gelesen statt einmalig zwischengespeichert.
- Zeilen-Rendering (Aktions-Label/-Icon/-Farbe) in ein gemeinsames Makro
  (_audit_log_macros.html) ausgelagert, das sowohl die Erstladung als auch
  das AJAX-Fragment (_audit_log_rows.html) nutzen -- verhindert Abweichungen
  zwischen beiden Darstellungen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 21:31:48 +02:00
alientimandClaude Sonnet 5 fc4b165d23 Auditlog: Ziel-Spalte wieder dynamisch statt erzwungen einzeilig (v1.1.8)
Das white-space:nowrap aus v1.1.7 sollte nur den Sonderfall des neuen
Auditlog-Export-Eintrags fixen, hat aber als Nebenwirkung die gesamte
Ziel-Spalte in die Breite gezogen: der bereits laenger bestehenden Aktion
data.export gehoert ein bis zu 135 Zeichen langes Ziel (Liste der
exportierten Kategorien), und table-layout:auto bemisst eine Spaltenbreite
anhand des LAENGSTEN unzeilenumbrochenen Inhalts ueber alle (auch gerade
nicht gefilterte) Zeilen hinweg -- nicht nur der sichtbaren.

Der eigentliche Ausloeser (zu langer Wert im Ziel-Feld) ist mit v1.1.7
bereits behoben (Ziel = kurzer Ressourcenname wie "Auditlog", Dateiliste
in Details) und macht das erzwungene nowrap ueberfluessig. Zeilenumbruch
bei den seltenen laengeren Zielen (wie data.export) ist unproblematisch,
genau wie es die Details-Spalte schon immer handhabt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 21:22:01 +02:00
alientimandClaude Sonnet 5 e81735d5ec Auditlog-Export: Ziel/Details korrekt getrennt (v1.1.7)
Beim Export archivierter Auditlog-Tage (log_action("auditlog.export_delete", ...))
stand faelschlich die komplette Dateiliste im Feld "Ziel" (dadurch wurde die
Spalte in der Auditlog-Tabelle wieder unschoen breit), statt wie bei allen
anderen Aktionen ueblich einen kurzen Ressourcennamen im Ziel und die
ausfuehrliche Beschreibung in Details zu haben. Jetzt: Ziel = "Auditlog",
Details = Anzahl + exakte Dateinamen. Gleiche Korrektur fuer den
automatischen Archivierungslauf selbst (auditlog.archive). "Ziel"-Spalte
zusaetzlich mit white-space:nowrap gegen Umbrueche bei laengeren Werten
abgesichert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 21:19:07 +02:00
alientimandClaude Sonnet 5 2eaefc9e1f Auditlog-Archivierung, Verlauf-RAW mit Zeilennummern, UI-Politur (v1.1.6)
- Auditlog waechst nur bis zu einem Schwellenwert (20.000 Zeilen): danach
  werden die aeltesten VOLLSTAENDIGEN Kalendertage (der laufende Tag wird
  nie mitten am Tag angefasst) tageweise in je eine Datei
  audit-YYYY-MM-DD.log unter /var/log/tesm/audit-archive archiviert und
  aus der DB geloescht, bis wieder 15.000 Zeilen unterschritten sind.
  Laeuft als taeglich pruefender Hintergrund-Thread (gate: TESM_WEB_PROCESS,
  wie beim bestehenden Fileshare-Sweep), zusaetzlich einmalig sofort beim
  Dienststart.
- Archivierte Auditlog-Tage sind unter Verlauf einsehbar (RAW-Ansicht) und
  koennen dort als ein ZIP exportiert werden, was sie danach vom Server
  loescht -- bewusst nur manuell ausgeloest statt automatisch, mit eigenem
  Recht logs_history.edit (neue "E"-Spalte bei Verlauf in der
  Gruppen-Rechtematrix, ergibt sich automatisch aus GROUP_ROW_TYPES).
  Aufbewahrung der Archivdateien selbst ist unbegrenzt; bei knappem
  Speicherplatz (< 1 GiB frei im Log-Verzeichnis) erscheint ein Warnhinweis
  auf der Verlauf-Seite.
- Verlauf -> "Komplettes Log (RAW)": Inhalt jetzt mit Zeilennummern (reine
  CSS-Counter-Loesung, kein Extra-Markup pro Zeile) -- Live-Log-RAW und
  nginx-RAW-Konfiguration bleiben bewusst unveraendert (nur an dieser
  Stelle angefragt).
- Auditlog-Tabelle: Avatar-Spalte entfernt, Spaltenbreiten (Zeitpunkt/
  Benutzer/Aktion/Ziel) schrumpfen jetzt auf den Inhalt statt zu strecken,
  "Ziel" nimmt nur noch so viel Platz wie noetig; Aktion-Pills brechen
  generell nicht mehr um (white-space: nowrap).
- "Änderungen" endgueltig zu "Auditlog" umbenannt (Recht-Label, Kommentare);
  die "Änderungen"-Filterkachel selbst (Bearbeiten-Kategorie) behaelt
  bewusst ihren Namen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 21:06:33 +02:00
alientimandClaude Sonnet 5 cb8b929c3c Auditlog: Hinzufuegen gruen, Loeschen rot, Bearbeiten orange (v1.1.5)
- Neue Pill-Farbvarianten (.action-pill--create/--delete/--edit) je nach
  Aktions-Art -- "create"/"upload"/"mkdir" gruen, "delete" rot, alles
  andere (bearbeiten/aktivieren/zuweisen/...) orange als Sammelkategorie.
- Fehlende Klartext-Label fuer die neuen fileshare.*-Aktionen ergaenzt
  (upload/mkdir/delete/rename/download) -- vorher stand dort noch der
  rohe Aktionsschluessel wie "fileshare.delete" statt eines lesbaren
  Textes wie bei allen anderen Aktionen.

Live auf POETEST verifiziert: alle drei Farben korrekt (gruen bei
"Datei(en) hochgeladen"/"Gruppe angelegt", rot bei "Datei/Ordner
gelöscht"/"Gruppe gelöscht", orange bei "Als ZIP heruntergeladen"/
"Gruppe bearbeitet").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 20:20:13 +02:00
alientimandClaude Sonnet 5 af5dfe30b4 Fileshare: Mehrfachauswahl zum Herunterladen (ZIP) und Loeschen (v1.1.4)
- Checkbox-Spalte in der Dateitabelle (inkl. "alle auswaehlen" im Tabellen-
  kopf) -- sobald mindestens ein Eintrag markiert ist, erscheint eine
  Aktionsleiste mit "Herunterladen (ZIP)" und (mit fileshare.edit)
  "Loeschen".
- Herunterladen buendelt die Auswahl serverseitig zu EINEM ZIP statt
  mehrerer einzelner Downloads (vermeidet Browser-Blockaden bei vielen
  gleichzeitigen Downloads) -- neue Route /fileshare/download-multi,
  Ordner werden dabei rekursiv mit aufgenommen (relativer Pfad als
  Archivpfad).
- Loeschen mehrerer Elemente auf einmal ueber eine neue Route
  /fileshare/delete-multi (ein Bestaetigungsdialog fuer die ganze Auswahl),
  mit Sammel-Erfolgsmeldung analog zum Mehrfach-Upload.
- Die Pfadaufloesung+Validierung fuer ein einzelnes Kind-Element wurde aus
  dem bestehenden Einzel-Loeschen in _fileshare_resolve_child() extrahiert
  und von Einzel-Loeschen, Mehrfach-Loeschen UND Mehrfach-Download
  gemeinsam genutzt (kein duplizierter Sicherheitscode).

Live auf POETEST verifiziert: Mehrfachauswahl inkl. "alle auswaehlen",
ZIP-Download zweier Dateien (Inhalt geprueft), ZIP-Download eines Ordners
(rekursiv, korrekter Archivpfad), Mehrfach-Loeschen mit Sammelmeldung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 20:10:21 +02:00
alientimandClaude Sonnet 5 efb0d2aa01 Gruppen-Bearbeiten als Modal, Mobile-Fix, Mehrfach-Upload (v1.1.3)
- Gruppen bearbeiten (Name + Rechte) laeuft jetzt ueber ein Modal statt der
  bisherigen Inline-Ausklapp-Zeile -- gleiche Optik wie "Neue Gruppe". Der
  Gruppenname war dabei bisher ein verstecktes, nie wirklich editierbares
  Feld; jetzt ein normales Texteingabefeld (Backend unterstuetzte das
  Umbenennen inkl. Systemgruppen-Schutz bereits vollstaendig, es fehlte nur
  die Eingabemoeglichkeit im Formular). Admin-Rechte-Ansicht und die
  "Freischalten"-Ausnahme fuer die Systemgruppe "Benutzer" ziehen ins
  jeweilige Modal mit um.
- Fileshare-Baum + Tabelle nebeneinander sprengte auf Tablet-/Handybreite
  die Seite -- stapelt jetzt (Baum oben, Tabelle darunter) ab der
  bestehenden 900px-Sidebar-Umschaltgrenze.
- Mehrfach-Upload: Datei-Eingabefeld erlaubt jetzt echte Mehrfachauswahl
  (mehrere Dateien in einem Dialog) UND mehrmaliges Hinzufuegen
  nacheinander (per DataTransfer angesammelt, bevor "Hochladen" gedrueckt
  wird) -- funktioniert nativ auch auf Mobilgeraeten, da kein Custom-
  Upload-Mechanismus noetig ist. Backend verarbeitet jetzt eine Liste
  statt einer einzelnen Datei (request.files.getlist), mit Sammel-
  Erfolgsmeldung und pro Datei separater Namensvalidierung.

Live auf POETEST verifiziert: Umbenennen+Rechte-Speichern ueber das neue
Modal, Admin-Modal (readonly), Mobile-Layout (390px, Baum stapelt korrekt),
echte Mehrfachauswahl (2 Dateien in einem Dialog, beide korrekt
hochgeladen und einzeln in der Sammelmeldung genannt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 19:17:32 +02:00
alientimandClaude Sonnet 5 d461f8ca42 Fileshare: redundanten Breadcrumb entfernt, Baum/Tabelle gleich hoch (v1.1.2)
- Der Breadcrumb-Link-Zeile ueber der Dateitabelle ("LDAP Share / Test")
  war seit der Baum-Navigation redundant -- der Baum zeigt die aktuelle
  Position ja schon (aufgeklappt + hervorgehoben). Entfernt.
- .fileshare-layout auf align-items:stretch umgestellt (statt flex-start)
  und .fileshare-main als Flex-Spalte mit table-wrap{flex:1}, damit die
  Baum- und die Tabellen-Kachel immer exakt gleich hoch sind, unabhaengig
  vom jeweiligen Inhalt. Das inline margin-bottom auf .table-wrap musste
  dafuer weg (sonst blieb trotzdem eine 16px-Luecke zwischen den unteren
  Kanten).

Live auf POETEST verifiziert: beide Kacheln exakt gleiche Hoehe (166.5px
in beiden Fällen), Breadcrumb-Div nicht mehr im DOM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 17:51:57 +02:00
alientimandClaude Sonnet 5 1b7fe81507 Fileshare: Baum-Navigation, Datei-Vorschau, 15MB-Uploads (v1.1.1)
- Freigaben-Auswahl von einem Dropdown auf eine Baum-Navigation (links)
  umgestellt: alle gemounteten Freigaben als Wurzelknoten, Ordner beliebig
  aufklappbar. Der Pfad von der Wurzel bis zur aktuellen Position wird
  serverseitig schon aufgeklappt gerendert (_fileshare_tree_ancestors),
  alles andere laedt der Client per Klick lazy nach (neue Route
  /fileshare/subfolders) -- kein Durchlaufen der kompletten Freigabe auf
  einmal noetig. Dateien stehen bewusst nicht im Baum, nur in der
  bestehenden Tabelle rechts.
- Inline-Vorschau fuer PDF (iframe), Bilder, TXT/CSV/LOG/MD/JSON (per Range-
  Request in Haeppchen bis 512KB, mit Hinweis bei Abschneiden -- Erkennung
  ueber den Content-Range-Header, nicht nur den 206-Status, der bei JEDEM
  Range-Request auftritt) sowie Word/Excel rein clientseitig per mammoth.js
  bzw. SheetJS (beide lokal vendored, kein Cloud-Viewer -- interne Dokumente
  verlassen das Netz nicht). Excel-Zellen werden bewusst per textContent
  statt ueber SheetJS' eigene HTML-Ausgabe gesetzt, um unabhaengig von
  deren Escaping-Verhalten sicher zu sein. Neue Route /fileshare/view mit
  fester Endungs-Positivliste, nosniff + restriktiver CSP.
- Uploads bis 15MB erlaubt (MAX_CONTENT_LENGTH in app.py) -- dafuer musste
  auch nginx' client_max_body_size (Standard 1MB) auf 16MB angehoben werden,
  sowohl im Vorlagen-Code (_NGINX_PROXY_LOCATIONS/_render_nginx_config,
  fuer die naechste Anwendung ueber Einstellungen->NGINX) als auch in der
  Fresh-Install-Vorlage. Neuer 413-Errorhandler zeigt eine Flash-Meldung
  statt Werkzeugs nackter Fehlerseite.
- install.sh: known_hosts (liegt unter /srv/tesm) von rsync --delete
  ausgenommen -- fehlte bisher in der Exclude-Liste (anders als sqlite.db/
  fernet.key/secret.key) und waere bei jedem Update verlorengegangen.

Live auf POETEST getestet: Baum-Vorbefuellung, Lazy-Load von
Geschwisterordnern, alle 5 Vorschautypen (PDF-Route direkt verifiziert,
Bild/Text/Word/Excel zusaetzlich visuell), Abschneide-Erkennung sowohl bei
kleiner als auch bei >512KB-Datei.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 17:40:33 +02:00
alientimandClaude Sonnet 5 2c9ec8a100 Fileshare: Hostname-Aufloesung fuer UNC-Pfade + Zuordnungen bearbeitbar
- Freigabe-Hostnamen (statt nur IP) werden jetzt unterstuetzt: mount.cifs
  bekommt die tatsaechliche Ziel-IP gezielt per "ip="-Option mitgegeben,
  aufgeloest ueber _resolve_unc_host_ip(). Deckt zwei reale Faelle ab, die
  beim Live-Test mit einem echten Server (Kurzname "s2025") auftraten:
  1. Ein bloer Kurzname ohne DNS-Suffixsuche loest auf diesem Server gar
     nicht auf -- Fallback probiert zusaetzlich den aus der LDAP-Server-
     Einstellung abgeleiteten AD-Domaenensuffix (a la ".ad.eertmoed.net").
  2. IPv4 wird bevorzugt aufgeloest, IPv6 nur als Fallback genutzt, falls
     fuer den Hostnamen keine IPv4-Adresse existiert.
  Der Hostname bleibt dabei unveraendert in der UNC sichtbar/gespeichert.
- AD-Gruppenzuordnungen (App-Rechte) und Fileshare-Gruppen sind jetzt per
  Bearbeiten-Button (Stift-Icon) direkt aenderbar statt nur loeschen+neu
  anlegen zu koennen -- neue edit_ldap_group_mapping/edit_fileshare_mapping
  POST-Routen (UPDATE per id), Bearbeiten-Modals vorbefuellt inkl.
  AD-Gruppen-Dropdown (gleiches Lade-Muster wie beim Anlegen).
- UX: "Erforderliche AD-Gruppe fuer Login" ist jetzt ein Dropdown mit
  "Gruppen laden" (identisches Muster wie die anderen AD-Gruppenfelder)
  statt eines Freitextfelds, in das der volle DN von Hand einzutragen war.
- _normalize_share_unc()-Hilfsfunktion aus dem Add-Handler herausgezogen,
  jetzt auch vom Edit-Handler genutzt (kein duplizierter Code).

Live auf POETEST verifiziert: Hostname-Mount ("s2025") erfolgreich nach
Fix, Bearbeiten-Modals korrekt vorbefuellt und persistiert, Dropdown laedt
55 AD-Gruppen und behaelt die aktuelle Auswahl beim Neuladen bei.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 16:56:40 +02:00
alientimandClaude Sonnet 5 09b222615a Fileshare-Feature: LDAP-gruppengesteuerte Netzlaufwerke im Browser (v1.1.0)
- Neue ldap_fileshare_mappings-Tabelle (AD-Gruppe -> Freigabe-UNC+Label),
  verwaltet in Einstellungen -> LDAP wie die bestehende App-Gruppenzuordnung
  (inkl. AD-Gruppen-Dropdown per "Gruppen laden", UNC-Backslash/Slash-
  Normalisierung).
- Login-Gate: optionale "Erforderliche AD-Gruppe fuer Login" (Dropdown,
  gleiches Lade-Muster) -- ohne Mitgliedschaft scheitert der Login wie bei
  falschen Zugangsdaten, ohne Hinweis auf den eigentlichen Grund.
- Sessionabhaengiges Mounten: beim LDAP-Login werden alle per AD-Gruppe
  zugeordneten Freigaben mit den eigenen (nie gespeicherten) Zugangsdaten
  per mount.cifs unter /mnt/tesm-shares/<session-token>/ gemountet, beim
  Logout wieder ausgehaengt. Passwort läuft ausschliesslich ueber die
  PASSWD-Umgebungsvariable von mount.cifs. Aufraeum-Sweep (30 Min-Takt,
  12h-Schwelle) und Cleanup-on-Startup als Sicherheitsnetz.
- Zwei unabhaengige Gates: TESM-Rechtesystem (fileshare.view/.create/.edit,
  neue PERMISSIONS-Gruppe fileshare_group) UND AD-Gruppenmitgliedschaft
  muessen beide zutreffen, sonst wird nicht gemountet und der Menuepunkt
  "Dateifreigaben" bleibt verborgen.
- Neuer Dateibrowser (/fileshare, .../download, .../upload, .../mkdir,
  .../delete, .../rename) mit Breadcrumbs, Upload/Download, Anlegen/
  Umbenennen/Loeschen -- Pfadaufloesung ausschliesslich ueber
  os.path.realpath-Vergleich gegen die eigene Mount-Root (Path-Traversal-
  Schutz), zusaetzlich secure_filename() fuer Datei-/Ordnernamen.
- KRITISCHER Bugfix waehrend Live-Test gefunden: generate_ips.py importiert
  app.py als Hilfsmodul (u.a. aus poe.sh/tesm-check.service heraus, alle
  paar Sekunden) -- das fuehrte Modul-Level-Code erneut aus und hat aktive
  Fileshare-Mounts anderer Sitzungen sofort wieder ausgehaengt. Neues
  TESM_WEB_PROCESS-Env-Flag (nur in tesm.service gesetzt) schuetzt
  Cleanup-on-Startup und den Sweep-Thread davor, bei einem reinen
  Modul-Import erneut zu laufen.
- install.sh: cifs-utils ergaenzt (liefert mount.cifs).
- SCHEMA_VERSION 1 -> 2, VERSION 1.0.10 -> 1.1.0.

Live auf Testbox (192.168.82.51) und POETEST (192.168.80.202) getestet:
Login-Gate (erlauben/verbieten/reset), Rechte-Gate (Mount+Menuepunkt nur
mit fileshare.view), vollstaendiger Datei-Workflow (Upload/Download/
Umbenennen/Anlegen/Loeschen) gegen eine echte SMB-Freigabe, Path-Traversal
gegen alle 5 Routen (serverseitig verifiziert, kein Escape), sauberes
Unmount bei /logout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 16:34:28 +02:00
alientimandClaude Sonnet 5 0074e48e09 Responsive-Fixes per echtem Playwright/DevTools-Test verifiziert, zwei Bugs behoben
Auf Nachfrage tatsaechlich mit Browser-DevTools (Playwright/Chromium,
lokal installiert) statt nur per CSS-Analyse geprueft -- echte
Screenshots + automatisierte Overflow-Pruefung auf iPhone-16-Pro- (393x852),
iPad- (820x1180 Hoch-/1180x820 Querformat) und Desktop-Viewport (1440x900)
gegen die echte Live-Instanz auf Testsystem-Update-Restart.

Dabei zwei echte Bugs gefunden und behoben:

1. Topbar-Ueberlappung (ALLE Seiten, jeder eingeloggte Nutzer, jedes
   iPhone/kleine Handy): .topbar-left (Seitentitel) und .topbar-right
   (Pruef-Countdown- + DHCP-Pille) sind beide flex:1 ohne Zeilenumbruch --
   auf 393px Breite quetscht das den Titel auf eine Breite, die kleiner
   ist als sein eigener Inhalt braucht, wodurch der Titeltext sichtbar
   ueber seine Spalte hinauslief und mit der Pille kollidierte (per
   getBoundingClientRect nachgewiesen: 24.5px Ueberlauf). Fix: ab 560px
   Breite bricht die Topbar um, Titel und Pillen bekommen je eine eigene
   Zeile.

2. HTTP-/HTTPS-Port-Felder auf der NGINX-Seite stapelten trotz
   entsprechender Media-Query-Regel nicht auf Handy-Breite -- Ursache:
   inline style="flex:1" auf den .field-Divs hat Vorrang vor jeder externen
   @media-Regel, ganz gleich wie spezifisch. Fix: inline style entfernt,
   neue Klasse .field--half stattdessen -- die Mobile-Stapel-Regel greift
   jetzt wie vorgesehen.

Zusaetzlich EINE eigene Aenderung aus v1.0.9 wieder zurueckgenommen, weil
sie sich beim Testen als unnoetig und riskant herausstellte:
- .table-wrap: overflow-x:auto (aus v1.0.9) wieder auf overflow:hidden
  zurueckgesetzt. Grund: JEDES Template mit .table-wrap hat bereits einen
  eigenen <div style="overflow-x:auto;"> exakt um die <table> -- die
  zusaetzliche Scroll-Faehigkeit der Karte selbst war komplett redundant
  und erzeugte einen verschachtelten doppelten Scroll-Container um
  denselben Inhalt. Beim Testen der aufklappbaren Rechte-Tabelle
  (Gruppen-Seite) fuehrte das zu Inhalten, die bei negativem x dauerhaft
  unsichtbar wurden. table.data-table { min-width: 560px; } bleibt
  bestehen (harmlos, der jeweils vorhandene innere Wrapper scrollt
  weiterhin korrekt).

Eine dritte vermutete Ursache (aufgeklappte Rechte-Tabelle "verschwindet"
beim Anklicken des Chevrons) stellte sich NICHT als echter App-Bug heraus,
sondern als Artefakt von Playwrights eigenem automatischem
"scroll-into-view-vor-Klick"-Verhalten (der Chevron sitzt in der letzten
Tabellenspalte) -- per direktem JS-Aufruf von toggleDetail() ohne
Playwright-Klick liess sich das nicht reproduzieren, die Seite verhaelt
sich für echte Nutzer korrekt.

Alle 8 getesteten Seiten x 4 Viewports (32 Kombinationen) zeigen nach den
Fixes keinerlei horizontalen Overflow mehr (dokumentseitenweite
scrollWidth == clientWidth in jedem Fall).

VERSION 1.0.9 -> 1.0.10.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 14:29:48 +02:00
alientimandClaude Sonnet 5 a3f5f028f2 Responsives CSS ausgebaut: Handy (iPhone) und Tablet (iPad) sauber nutzbar
Bestehendes eigenes Design-System beibehalten (kein Bootstrap-Framework
eingebunden), stattdessen die vorhandenen Media Queries/Breakpoints
(Sidebar kollabiert bereits ab 900px) um die fehlenden Faelle ergaenzt:

- .table-wrap: overflow:hidden -> overflow-x:auto. Tabellen mit vielen
  Spalten (Geraete, Switche, Papierkorb, Benutzer, ...) haben auf
  schmalen Screens bisher entweder Spalten lautlos abgeschnitten oder
  die ganze Seite waagerecht aufgerissen -- jetzt scrollt NUR die
  Tabelle selbst (gleiches Prinzip wie Bootstraps .table-responsive).
  table.data-table bekommt zusaetzlich eine min-width als sinnvolle
  Scroll-Schwelle.
- .settings-grid: minmax(340px, 1fr) -> minmax(min(340px, 100%), 1fr) --
  ein nackter Pixel-Mindestwert erzwingt auf einem 375px-iPhone sonst
  waagerechten Overflow der ganzen Seite, sobald die Spalte breiter als
  der verfuegbare Platz ist. Neue Modifier-Klasse .settings-grid--wide
  (420px-Variante) ersetzt zwei betroffene inline
  style="grid-template-columns:minmax(420px,...)"-Overrides (LDAP-,
  NGINX-Seite), die denselben 100%-Fallback nicht hatten.
- .permission-group-col: eigenes overflow-x:auto zusaetzlich zum
  bestehenden flex-wrap der Elternzeile -- falls eine einzelne
  Rechte-Spalte (z.B. Geraete mit R/W/E/D) auch nach dem Umbruch auf
  eine eigene Zeile noch zu breit ist.
- Ab 900px (bestehender Sidebar-Breakpoint) zusaetzlich: .flex (generische
  Button-/Toolbar-Zeilen), .section-head, .term-toolbar/.log-toolbar und
  .detail-row brechen um statt seitlich ueberzulaufen; .modal-footer
  ebenso; .search-input in Tabellen-Toolbars schrumpft flexibel.
- Ab 640px: nebeneinander angeordnete Formularfelder (z.B. HTTP-/HTTPS-
  Port bei NGINX) stapeln sich auf volle Breite statt zwei sehr schmale
  Eingaben zu erzwingen; Modal-Innenabstand etwas schmaler.
- Ab 560px: Buttons in Kopfzeilen (section-head) nehmen die volle Breite
  ein statt als schmaler Block rechts zu kleben.

Live auf Testsystem-Update-Restart verifiziert: alle Kernseiten
(Dashboard, Clients, Switche, Gruppen, LDAP, NGINX, Live-Log, Wartung)
laden weiterhin fehlerfrei (200) nach dem CSS-/Template-Austausch.

WICHTIGER HINWEIS: In dieser Umgebung steht kein Browser-Screenshot-
Werkzeug zur Verfuegung, die Aenderungen wurden also nicht pixelgenau auf
echten iPhone-16-Pro-/iPad-Bildschirmgroessen gegengeprueft, sondern rein
ueber CSS-Analyse und Seiten-Ladetests. Ein Blick auf einem echten Geraet
(oder per Browser-Devtools-Emulation) wird empfohlen.

VERSION 1.0.8 -> 1.0.9.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 12:34:35 +02:00
alientimandClaude Sonnet 5 5bca29f3da NGINX-Seite: Sichtbarkeit/Kontrolle der automatischen Let's-Encrypt-Verlaengerung
certbot richtet die automatische Verlaengerung (certbot.timer, zweimal
taeglich) zwar schon beim Paket-Install selbst ein -- das war fuer den
Admin in der Weboberflaeche bisher aber unsichtbar, reine Behauptung im
Code-Kommentar. Jetzt sichtbar/nachpruefbar:

- Neue Zeile "Automatische Verlaengerung" im Zertifikat-Status (nur bei
  Let's-Encrypt-Zertifikaten): Timer aktiv/inaktiv + naechster geplanter
  Lauf (_certbot_timer_status(), liest systemctl is-active/list-timers).
- "Verlaengerung testen (Dry-Run)" -- certbot renew --dry-run, aendert
  nichts, zeigt nur ob die Verlaengerung im Ernstfall funktionieren wuerde.
- "Jetzt manuell verlaengern" -- certbot renew --force-renewal, fuer
  Testzwecke oder eine sofortige Schluessel-Rotation (mit Warnhinweis auf
  Let's Encrypts Rate-Limits).
- Hinweistext beim Anfordern zeigt den Timer-Status schon VOR der ersten
  Anforderung.

VERSION 1.0.7 -> 1.0.8 (ab hier wieder normale fortlaufende
Versionierung statt erneutem Release derselben Versionsnummer).

Live auf Testsystem-Update-Restart verifiziert: Seite laedt weiterhin
fehlerfrei (_certbot_timer_status crasht nicht ohne aktives
Let's-Encrypt-Zertifikat), certbot.timer selbst bestaetigt aktiv
(naechster Lauf laut systemctl list-timers korrekt angezeigt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 12:24:15 +02:00
alientimandClaude Sonnet 5 4de5828573 NGINX-Seite ausgebaut: Domain/Ports, RAW-Config, Let's Encrypt mit Auto-Verlaengerung; Flask nur noch lokal erreichbar
Umbenennung: Einstellungen -> SSL/TLS heisst jetzt Einstellungen -> NGINX
(Route /settings/ssl -> /settings/nginx, Recht settings_ssl.* ->
settings_nginx.*, Icon "server" statt "shield") -- die Seite verwaltet
den kompletten Reverse-Proxy, nicht mehr nur SSL.

Neu:
- Domain (server_name) und HTTP-/HTTPS-Port sind jetzt einstellbar, nicht
  mehr hart "_" bzw. 80/443. Aendert man den HTTP-Port weg von 80, wird
  automatisch zusaetzlich ein minimaler Nur-ACME-Block auf Port 80
  mitgeschrieben, da Let's Encrypt (HTTP-01) protokollbedingt immer Port 80
  braucht, unabhaengig vom konfigurierten HTTP-Port.
- "RAW-Konfiguration"-Button zeigt die komplette aktuelle nginx-Config im
  selben Popup-Stil wie das Live-Log (Wiederverwendung von
  window.openRawLogModal, kein neuer Code dafuer noetig).
- Let's Encrypt: Zertifikat per certbot anfordern (certonly --webroot,
  bewusst NICHT das certbot-nginx-Plugin -- das wuerde die App-eigene,
  komplett selbstgeschriebene Config direkt editieren und bei der
  naechsten Aenderung ueber diese Seite wieder verlieren). Automatische
  Verlaengerung laeuft ueber certbots eigenen systemd-Timer/Cron
  (Standard-Paketverhalten), ein Deploy-Hook
  (/etc/letsencrypt/renewal-hooks/deploy/tesm-reload-nginx.sh, wird wie
  die logrotate-Config bei jedem App-Start neu geschrieben) laedt nginx
  nach jeder Verlaengerung neu. Fehlgeschlagene Anforderung (kein
  oeffentlicher DNS-Eintrag o.ae.) wird sauber abgefangen und als
  Fehlermeldung angezeigt, ohne etwas zu veraendern -- live mit einer
  ungueltigen Test-Domain verifiziert.
- Zertifikatsquelle (Upload vs. Let's Encrypt) wird als eigenes Setting
  (ssl_source) verfolgt, das Anwenden/Anzeigen/Entfernen beruecksichtigt
  beide Quellen jeweils passend (Entfernen einer LE-verwalteten
  Installation laeuft ueber "certbot delete", nicht ueber rohes
  Datei-Loeschen, damit certbots eigene Renewal-Konfiguration nicht
  verwaist).
- certbot zu install.sh's Paketliste hinzugefuegt.

Flask/gunicorn bindet jetzt an 127.0.0.1 statt 0.0.0.0 (tesm.service) --
direkter Zugriff auf Port 5000 aus dem Netz ist damit nicht mehr moeglich,
nur noch ueber den nginx-Reverse-Proxy (der ohnehin nur ueber 127.0.0.1
zugreift). Beantwortet die Nachfrage, ob Flask unerreichbar gemacht werden
kann.

Live auf Testsystem-Update-Restart verifiziert: Domain/Port-Aenderung
(HTTP-Port 8080 inkl. automatischem Zusatz-Block auf Port 80),
RAW-Config-Anzeige, Zertifikat-Upload, Let's-Encrypt-Fehlschlag sauber
abgefangen, Rueckbau auf Standardwerte (_, 80, 443, kein SSL) ueber
denselben Anwenden/Bestaetigen-Sicherheitsnetz-Pfad wie zuvor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 12:15:32 +02:00
alientimandClaude Sonnet 5 f27dde466b SSL/TLS-Verwaltung (Upload+Aktivieren), install.sh-Idempotenz, poe_web/rpi-check-Cleanup entfernt
SSL/TLS (neue Seite Einstellungen -> SSL/TLS, eigenes Recht settings_ssl.view/.edit):
- Zertifikat+privater Schluessel per Upload hinterlegbar (PEM), Validierung
  VOR dem Speichern: gueltiges Zertifikat, noch nicht abgelaufen, Schluessel
  passt zum Zertifikat (Abgleich ueber die oeffentlichen Schluessel als
  DER-Bytes -- funktioniert algorithmus-unabhaengig fuer RSA/EC/...).
  Speicherort /etc/tesm/ssl (bewusst ausserhalb von /srv/tesm, damit
  rsync --delete beim Update das Zertifikat nicht mitloescht), Key mit
  chmod 600.
- HTTPS aktivieren schreibt/validiert (nginx -t) eine neue nginx-Konfiguration
  und laedt erst danach neu; Port 80 leitet dann automatisch auf https weiter.
  HSTS optional zuschaltbar, setzt aktives SSL voraus.
- Gleiches Sicherheitsnetz wie bei den Netzwerkeinstellungen: nach dem
  Anwenden 45s Zeitfenster zum Bestaetigen, sonst automatischer Rueckroll
  der nginx-Konfiguration UND der ssl_enabled/hsts_enabled-Einstellungen.
  Beide Pfade (Timeout-Rollback und Bestaetigen-verhindert-Rollback) live
  auf Testsystem-Update-Restart verifiziert, inklusive eines ECHTEN,
  ungeplant ausgeloesten Rollbacks waehrend des Testens.
- Noch KEINE Let's-Encrypt-Automatisierung (siehe Kommentar im Code) --
  alle bekannten Instanzen laufen intern ohne oeffentliche Domain, HTTP-01
  wuerde dort nicht funktionieren; kann bei Bedarf separat ergaenzt werden.

install.sh: Update-Idempotenz ("nur anfassen, was sich geaendert hat"):
- install_if_changed()-Helfer: Custom-Skript/systemd-Units/MOTD-Banner
  werden nur noch bei tatsaechlich geaendertem Inhalt neu geschrieben,
  daemon-reload nur wenn sich mindestens eine Unit geaendert hat.
- nginx-Konfiguration wird nur noch bei einer Frischinstallation aus dem
  Repo-Template geschrieben -- eine bereits vorhandene (egal ob unveraendert
  oder von der App selbst um SSL erweitert) wird nie wieder ueberschrieben.
  Voraussetzung dafuer, dass die neue SSL-Funktion ein Update ueberlebt.

update.sh: komplette Sonderbehandlung fuer die zwei TESM vorausgehenden
Generationen (rpi-check.service, poe_web.service/poe_manager) entfernt --
auf allen aktuellen Installationen laengst migriert. Deaktivierte
Unit-Dateien dieser Generationen wurden auf Testsystem-Update-Restart
zusaetzlich manuell endgueltig geloescht (waren dort seit einer Migration
im September 2025 nur deaktiviert, nicht entfernt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 11:56:55 +02:00
alientimandClaude Sonnet 5 8253ab3f0e Verlauf: aktuelle live.log als Eintrag, Auswahl per Zeitraum statt Dateiname; RAW-Darstellung
- _live_log_history_files() liefert jetzt zusätzlich generation 0 = die
  aktuelle, noch laufende live.log selbst (nicht nur die rotierten
  Kopien .1/.2/...) -- _resolve_log_history_file akzeptiert 'live.log'
  als gültige Auswahl.
- Jeder Eintrag bekommt ein range_label ('von' - 'bis'): 'bis' ist das
  eigene mtime (Zeitpunkt der Rotation) bzw. 'laufend' bei der aktuellen
  Datei, 'von' ist das mtime der naechstaelteren Generation -- rename()
  bei einer Rotation aendert mtime nicht, jede Generation behaelt also
  dauerhaft ihren eigenen Entstehungszeitpunkt. Live verifiziert: die
  Zeitraeume reihen sich lueckenlos aneinander (Ende von Generation N ==
  Anfang von Generation N-1).
- Dropdown und Toolbar zeigen jetzt den Zeitraum statt des rohen
  Dateinamens.
- logs_history.html: Farbcodierung (colorizeLine) entfernt, log-box durch
  ein <pre class="raw-log-content"> ersetzt -- reine RAW-Darstellung
  ohne Client-seitiges Span-pro-Zeile-Aufbauen, auf Wunsch zur weiteren
  Reduktion der Ladezeit im Verlauf (Live-Ansicht selbst bleibt farbig).
- VERSION 1.0.6 -> 1.0.7.

Live getestet auf Testsystem-Update-Restart: 4 Eintraege (aktuell + 3
rotierte Generationen), Zeitraeume lueckenlos verkettet, RAW-Endpoint für
'file=live.log' funktioniert, Path-Traversal-Versuch weiterhin mit 404
abgewiesen.

Zusaetzlich untersucht (kein Code-Fix, siehe Konversation): der Verdacht
eines install.sh-Bugs, der tesm-check.service nach einem Update gestoppt
liesse. journalctl zeigt auf beiden Boxen durchgehend 'active (running)'
ueber den fraglichen Zeitraum -- keine Bestaetigung eines Defekts in der
Dienstverwaltung gefunden. Wahrscheinlichste Erklaerung: ein manuell zu
Testzwecken erzwungener logrotate-Lauf (logrotate -f) direkt nach dem
Update hat live.log geleert, wodurch die Live-Log-Seite und die
Naechste-Pruefung-Anzeige fuer die Dauer eines Pruefintervalls leer
wirkten, bis der naechste Zyklus neuen Inhalt schrieb.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 11:28:15 +02:00
alientimandClaude Sonnet 5 99d4d3cd01 Live-Log: 300-Zeilen-Tail, RAW-Popup und Log-Verlauf (History) mit eigenem Recht
- /logs und /get_log liefern nur noch die letzten 300 Zeilen (_tail_lines,
  effizientes Rueckwaerts-Lesen ohne komplette Datei einzulesen) statt der
  kompletten live.log -- behebt Ladezeit-Probleme auf groesseren Umgebungen.
- Neuer Endpoint /logs/live/raw liefert das komplette Live-Log fuer ein
  Popup im Stil des bestehenden 'Ungespeicherte Aenderungen'-Modals
  (window.openRawLogModal in app.js, lazy nachgeladen erst bei Klick).
- Neue Unterseite 'Verlauf' (/logs/history, /logs/history/raw) fuer aeltere,
  von logrotate rotierte Kopien des Live-Logs (live.log.1, .2, ...),
  auswaehlbar per Dropdown, ebenfalls mit 300-Zeilen-Tail + RAW-Popup.
- Eigenes Recht logs_history.view (Kind von logs_group, direkt unter Live
  einsortiert) statt an logs_live.view gekoppelt -- Live-Status sehen und
  durch alte Logs blaettern sind bewusst getrennte Rechte.
- Dateiname-Validierung gegen Path-Traversal in _resolve_log_history_file
  (striktes Regex + Verzeichnis-Check + Existenzpruefung).
- VERSION 1.0.4 -> 1.0.5.

Live getestet auf Testsystem-Update-Restart (192.168.82.51): Tail-Anzeige,
RAW-Popup, Verlauf-Auswahl inkl. echter live.log.1, Permission-Matrix
(Gruppen-Seite) sowie Rechtetrennung (Nutzer mit nur logs_live.view sieht
/logs, aber weder Verlauf-Link noch Zugriff auf /logs/history bzw.
/logs/history/raw) und Path-Traversal-Abwehr allesamt verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 10:53:56 +02:00
alientim e934c3ff0f Wartung: Update-Button orange + nach rechts, Bulk-Button 'Bulk-Update'
Reihenfolge in der Aktionen-Spalte getauscht (Neustart -> Update, statt
Update -> Neustart) und der Einzel-Update-Button auf btn-primary
(bereits vorhandene Akzentfarbe, orange) umgestellt, damit er sich vom
neutralen Neustart-Button abhebt. Der Bulk-Button unten heisst jetzt
'Bulk-Update' statt 'Update starten'.

Live end-to-end verifiziert: Testsystem-Update-Restart hat eine neue
MAC/IP bekommen (52:54:00:AD:B1:73 / 192.168.82.51, Geraete-Eintrag in
POETEST entsprechend aktualisiert) -- der neue Einzel-Update-Button hat
darueber ein komplettes Update ausgeloest und erfolgreich durchlaufen
(alle 6 Schritte einzeln bestaetigt: apt update, initramfs
MODULES=most, apt upgrade, apt full-upgrade, apt clean, apt
autoremove).
2026-08-22 10:00:40 +02:00
alientim 7b17c952ba Papierkorb: Sortieren/Suchen; Wartung: Einzel-Update-Button + Bulk-Neustart
Papierkorb: jede der fuenf Kategorien (Clients/Switche/Zugangsdaten/
Benutzer/Gruppen) bekommt jetzt dasselbe Sortieren/Suchen wie die
uebrigen Listenseiten (data-sortable + Such-Eingabe je Tabelle, nur
wenn die Kategorie tatsaechlich Eintraege hat).

Wartung: Update liess sich bisher nur per Bulk (Checkbox + 'Update
starten') ausloesen. Jedes Geraet bekommt jetzt zusaetzlich einen
eigenen 'Update'-Button (analog zum bereits vorhandenen 'Neustart'-
Button, ueber ein eigenstaendiges <form> + form="update-<mac>"-
Referenz -- kein verschachteltes <form>). Zusaetzlich ein 'Bulk-
Neustart'-Button neben 'Update starten', der dieselben Checkboxen/
dasselbe #updateForm nutzt wie das Bulk-Update (Ziel-Route wird per JS
nur fuer diesen einen Submit umgebogen), inkl. Bestaetigungsdialog wie
beim Einzel-Neustart.

Serverseitig war dafuer keine Aenderung noetig -- _maintenance_dispatch()
verarbeitete beliebig viele MACs schon immer generisch, die bisherigen
Formulare haben nur zufaellig immer genau eine bzw. alle uebermittelt.

Live auf POETEST verifiziert: Papierkorb zeigt Sortieren/Suchen nur bei
tatsaechlich befuellten Kategorien; der neue Einzel-Update-Button loest
korrekt einen Job fuer genau dieses eine Geraet aus (SSH-Verbindungs-
versuch zum richtigen Ziel bestaetigt, sauber behandelter Fehler, da das
Testsystem gerade offline war).
2026-08-22 09:53:28 +02:00
alientim d90095885f Papierkorb: eigener globaler Bereich unter Einstellungen (view+edit)
Vorher: Sichtbarkeit einer Papierkorb-Sektion war an das jeweilige
Ressourcen-Leserecht gekoppelt (devices.view fuer Clients-Sektion usw.),
Wiederherstellen/endgueltig Loeschen an das jeweilige Ressourcen-
Bearbeiten-Recht (switches.edit fuer Switche im Papierkorb usw.).

Jetzt: papierkorb.view/papierkorb.edit als eigenstaendiges Rechtepaar
unter Einstellungen (vorher unter Geraete einsortiert), komplett
losgeloest von den einzelnen Ressourcen-Rechten:
- papierkorb.view zeigt ALLE fuenf Kategorien auf einmal, unabhaengig
  davon, ob der Benutzer die jeweilige aktive Ressource sehen darf.
- papierkorb.edit erlaubt Wiederherstellen/endgueltig Loeschen JEDER
  Kategorie, unabhaengig vom sonstigen Bearbeiten-Recht der jeweiligen
  Ressource (z.B. switches.edit gewaehrt jetzt KEINEN Zugriff mehr auf
  Switche im Papierkorb).

Live auf POETEST mit zwei eigens angelegten Testgruppen verifiziert:
ein Nutzer mit switches.edit (aber ohne papierkorb.edit) kann einen
Switch im Papierkorb nachweislich NICHT wiederherstellen (deleted_at
unveraendert vor/nach dem Versuch); ein Nutzer mit papierkorb.edit
(aber ohne switches.edit) kann es.
2026-08-22 09:45:05 +02:00
alientim 33753e4f41 Erklärende Kommentare aus dem Code entfernt, in KNOWLEDGE_BASE.md überführt
Betrifft app.py, create_db.py, poe.sh sowie alle HTML-Templates mit
Kommentaren (Jinja {# #}, HTML <!-- -->, eingebettetes JavaScript //).
Docstrings bleiben unangetastet -- nur reine Kommentarzeilen/-fragmente
wurden entfernt, damit der Code selbst schlank bleibt, ohne die
dahinterliegende Begründung zu verlieren.

Umsetzung:
- app.py/create_db.py: per Pythons eigenem tokenize-Modul entfernt (sicher
  gegen '#' innerhalb von String-Literalen, Shebang-Zeile ausgenommen).
- poe.sh: manuell, da einzelne expect-Prompt-Strings ('expect "#"')
  ein literales '#' als Teil des Strings enthalten, kein Kommentar.
- Templates: {# #}/<!-- --> per Regex (eindeutige Delimiter, anschließend
  mit Jinja2s eigenem Parser validiert), echte // -JS-Kommentare manuell
  identifiziert (zwei Fundstellen mit '//' waren tatsächlich Code --
   als Protocol-relative-URL-Template-
  String -- und wurden bewusst nicht angefasst).

Verifiziert: alle Python-/Bash-/Jinja-Dateien syntaktisch weiterhin
gültig, live auf der Testbox deployt und alle 14 Kernrouten mit 200
bestätigt, keine Fehler im App-Log.
2026-08-14 12:05:39 +02:00
alientim d72db26a86 Versionsnummer in der UI anzeigen (Login-Seite + Sidebar-Footer)
VERSION-Datei (neben app.py, wird von install.sh 1:1 nach /srv/tesm
mitkopiert) plus Context-Processor inject_tesm_version() -- die
zugehoerige app.py-Aenderung war bereits im vorherigen Commit
enthalten. Damit laesst sich der installierte Stand direkt in der
Oberflaeche ablesen, ohne Datei-Zeitstempel oder Commit-Hashes
vergleichen zu muessen.
2026-08-13 23:56:32 +02:00
alientim 93bcf4f6b6 DHCP: Auto-Write+Neustart nach Änderungen, Lease löschen, Dedup, Autostart-Slider
- Automatisches Schreiben+Neustart: jede relevante Änderung (Reservierung,
  Option, Subnetz, globale Einstellung) ruft jetzt _dhcp_write_and_restart()
  direkt nach dem Speichern auf -- schreibt die Kea-Config neu und startet
  den Dienst neu, FALLS er gerade aktiv ist (kein ungefragtes Hochfahren
  eines bewusst gestoppten Dienstes). Ein manuelles "Datei schreiben" +
  "Neu starten" kann dadurch nicht mehr vergessen werden. Der bisherige
  "Speichern"-Button (write_dhcp_file) restartet jetzt ebenfalls
  automatisch statt nur zu schreiben.
- Leases-Übersicht dedupliziert: Keas memfile-Backend hängt bei jeder
  Verlängerung eine neue Zeile an, statt die alte zu ersetzen -- dieselbe
  Adresse stand oft dutzendfach in der CSV. _dhcp_read_leases() zeigt jetzt
  nur noch die jeweils neueste (per "expire") Zeile pro IP.
- Einzelne Leases löschen (neuer Button + Route): entfernt alle Zeilen
  einer Adresse aus der Lease-CSV und startet den Dienst neu, damit Kea sie
  beim Neuladen nicht aus dem Speicher zurückschreibt.
- Eigenständiger Autostart-Slider (wie beim Aktivieren/Deaktivieren von
  Clients) -- steuert nur systemctl enable/disable, entkoppelt vom
  aktuellen Start/Stopp-Zustand.
- KRITISCH, live gefunden und behoben: die erste Version von
  _dhcp_delete_lease() hat Kea beim Neustart zum Absturz gebracht --
  Pythons csv-Modul schreibt standardmäßig "\r\n"-Zeilenenden, Kea erwartet
  aber reines "\n" und lehnte die Datei mit "invalid column: 'pool_id^M'"
  komplett ab. Zusätzlich hätte das Neuanlegen der Datei als root (der
  App-Prozess) sie root:root statt dem Kea-Dienstnutzer gehören lassen,
  wodurch Kea keine neuen Leases mehr hätte schreiben können. Beides
  gefixt (lineterminator="\n", Eigentümer/Rechte der Originaldatei nach dem
  Schreiben wiederhergestellt) und auf POETEST bestätigt: Dienst übersteht
  den Neustart jetzt, Datei bleibt _kea:_kea, keine ^M mehr.
2026-08-13 19:11:34 +02:00
alientim 4c13127679 Wartung: Update-Ausgabe live streamen, Zeitstempel pro Zeile, Auto-Scroll
- Neue _run_ssh_command_streaming(): liest stdout/stderr zeilenweise WÄHREND
  das SSH-Update noch läuft, statt alles erst nach Abschluss auf einmal zu
  liefern. Jede Zeile bekommt sofort einen eigenen Zeitstempel und wird per
  _maintenance_job_set() live in den Job-Status geschrieben (bereits alle
  2s gepollt) -- Puffer auf die letzten 400 Zeilen begrenzt statt eines
  harten Zeichen-Limits.
- Eindeutiger Abschluss-Marker als letzte Ausgabezeile: "✔ All Updates
  Done" bei Erfolg, "✖ Update abgebrochen/fehlgeschlagen …" bei Fehler --
  vorher endete die Ausgabe einfach mitten im rohen apt-Output.
- Frontend: Ausgabefenster scrollt jetzt automatisch mit, sobald neue
  Zeilen reinkommen ("stick to bottom", solange man nicht selbst
  hochgescrollt hat) und beim ersten Öffnen direkt ans Ende.
- Live auf POETEST verifiziert: Zeilen tragen korrekte Zeitstempel, Update
  endet sichtbar mit "✔ All Updates Done".
2026-08-13 16:02:07 +02:00
alientim 51d105399b Kritischen poe.sh-Pfadbug beheben + verschachteltes <form> in Wartung fixen
- KRITISCH: usr/local/bin/custom/poe.sh wurde bei der TESM-Umbenennung
  übersehen und zeigte weiterhin komplett auf die alten Pfade
  (/srv/poe_manager/sqlite.db, /srv/poe_manager/generate_ips.py,
  /var/log/poe-manager) -- der Ping-Check/PoE-Neustart-Loop
  (tesm-check.service) lief dadurch seit dem Umzug nur noch auf Fehler
  ("No such file or directory"), das Live-Log stand seit gestern 19 Uhr
  still und automatische PoE-Neustarts bei Geräteausfall liefen die
  ganze Zeit ins Leere. Auf POETEST live behoben und verifiziert: Log
  schreibt wieder frische Einträge, Checks laufen wieder durch.
- Wartung: "Update starten" nahm live IMMER zusätzlich das erste Gerät
  mit, auch ganz ohne angehakte Checkbox. Ursache: der "Neustart"-Button
  jeder Zeile saß in einem eigenen <form>, das INNERHALB des großen
  #updateForm verschachtelt war -- ungültiges HTML, wodurch Browser das
  äußere Formular vorzeitig schließen/aufspalten. Behoben, indem jedes
  Neustart-<form> jetzt außerhalb von #updateForm steht und der Button
  es nur per form="reboot-<mac>" referenziert. Live verifiziert: ein
  gezielt einzeln ausgewähltes Gerät startet jetzt ausschließlich sich
  selbst, keine leere Auswahl mehr möglich.
- Zusätzlich defensiv: Checkboxen werden beim Laden der Wartungsseite
  jetzt explizit zurückgesetzt (manche Browser stellen den
  Checked-Zustand nach einem Redirect/Reload sonst aus dem Verlauf
  wieder her).
- Restliche "poe"-Relikte aus der TESM-Umbenennung bereinigt: Export-
  Dateiname (poe_manager_export_… → tesm_export_…), Kommentare
  (poe_web.service → tesm.service) sowie ein für den Nutzer sichtbarer
  Hinweistext in Systemeinstellungen (rpi-check.service → tesm-check.service).
2026-08-13 15:54:40 +02:00
alientim 34d9cdebd6 Netzwerkeinstellungen: Mehrere Interfaces korrekt unterstützen
- Kritischer Bug behoben: _apply_network_config() baute die netplan-
  Override-Datei bei JEDEM Speichern komplett neu auf (nur mit dem gerade
  bearbeiteten Interface) statt bestehende Einträge anderer Interfaces zu
  erhalten -- ein zweites Interface einzurichten hat dadurch die statische
  Konfiguration des ersten aus der Datei geworfen (fiel auf DHCP zurück).
  Jetzt wird die vorhandene Datei eingelesen und nur der Block des
  gespeicherten Interfaces ersetzt/ergänzt.
- Live auf POETEST reproduziert und nach dem Fix verifiziert: enp1s0 verlor
  durch den Bug seine statische 192.168.80.202/24 (fiel auf DHCP zurück),
  während enp3s0 (zweites, neu eingerichtetes Interface) 192.168.82.202/24
  behielt. Nach dem Fix enp1s0 über die echte GUI-Route wiederhergestellt
  (Werte aus dem aktiven DHCP-Lease bestätigt) -- beide Interfaces stehen
  jetzt nebeneinander in derselben netplan-Datei.
- Neuer JSON-Endpunkt /settings/network_state + JS: die Eingabemaske
  (Modus/IP/Prefix/Gateway/DNS) lädt beim Umschalten des Interface-Dropdowns
  jetzt dessen tatsächlichen Ist-Zustand nach, statt weiter die Werte des
  zuvor angezeigten Interfaces zu zeigen.
- Die Übersicht ganz oben auf der Karte zeigt jetzt JEDES aktive Interface
  gleichzeitig (eigene Zeile mit IP/Gateway/Modus/DNS) statt nur das gerade
  im Formular ausgewählte.
- net_interface-Setting wird jetzt tatsächlich persistiert (war vorbereitet,
  aber nie geschrieben), damit die Seite nach einem Reload das zuletzt
  bearbeitete statt immer nur das alphabetisch erste Interface vorbelegt.
2026-08-13 13:00:02 +02:00
alientim 3ab3407a18 Login-Seite überarbeitet, TESM-Logo als echter Hintergrund; SSH-MOTD-Banner mit Live-Daten
- Login-Seite: WiS-/TESM-Logo und "Melde dich an, um fortzufahren" aus der
  Box entfernt; die TESM-Wortmarke mit Funktionszeile ist jetzt der
  tatsächliche Seitenhintergrund (absolut positioniert, hinter allem), die
  Box liegt zentriert davor. Nebenbei zwei echte Bugs gefunden und behoben:
  fehlendes Cache-Busting auf login.html (asset_url() wie in base.html,
  sonst hält nginx' 7-Tage-Cache eine alte style.css/app.js fest) und ein
  durch den fehlenden align-items-Wert auf die volle Höhe gestrecktes
  Login-Formular.
- Neuer SSH-Login-Banner (dynamisches MOTD) mit Live-Flottenstatus statt der
  Standard-Ubuntu-Willkommensmeldung: srv/tesm/motd_banner.py liest
  Online/Offline- und DHCP-Status aus denselben Quellen wie das
  Web-Dashboard (get_device_status()/_dhcp_read_leases()), eingebunden über
  etc/update-motd.d/50-tesm. install.sh deaktiviert dabei die Standard-
  Ubuntu-MOTD-Skripte (chmod -x, reversibel) und installiert den neuen Hook.
2026-08-13 09:13:18 +02:00
alientim d1b10bd970 Rebrand to TESM: infra rename, LDAP/AD login, selective import/export, Papierkorb
- Vollständiges Rebranding "PoE Manager" -> TESM (TimEShepManager): neue
  Logos/Favicon (theme-aware), Sidebar/Login-Branding, Copyright-Zeile.
- Infrastruktur-Umbenennung: srv/poe_manager -> srv/tesm, alle POE_*-Env-Vars
  -> TESM_* (POE_SCRIPT bewusst unverändert, echtes PoE-Skript), systemd
  Units poe_web/rpi-check* -> tesm/tesm-check*, nginx-Site, netplan/logrotate
  Configs, Gitea-Repo alientim/Aruba-PoE-Modern -> alientim/tesm.
- LDAP/Active-Directory Same-Sign-On: Search+Bind, AD-Gruppen->App-Gruppen-
  Zuordnung (additiv), Konto-Sperren, AD-Vorab-Suche/-Anlage, eigene
  Einstellungsseite.
- Selektives Import/Export (8 Kategorien, Zwei-Schritt-Vorschau) mit eigenem
  R/E/X-Rechtemodell (X = Export, getrennt von E = Import).
- Papierkorb: Soft-Delete statt Hard-Delete für Geräte/Switche/Zugangsdaten/
  Benutzer/Gruppen (AD-Nutzer ausgenommen), eigene Seite unterhalb Wartung,
  konfigurierbare Aufbewahrungsfrist.
- rpi_ip -> ip Spalten-/Code-Umbenennung (Geräte sind längst nicht mehr auf
  Raspberry Pis beschränkt).
- README auf aktuellen Stand gebracht.
2026-08-12 22:21:52 +02:00