Commit Graph
104 Commits
Author SHA1 Message Date
alientim 9f4195b800 usr/local/bin/custom/poe.sh aktualisiert 2026-08-13 23:13:39 +02:00
alientim d066dbf03d SSH-Verbindungstest: 'password' vor 'keyboard-interactive' bevorzugen
Bei einigen Switches/APs (u.a. ArubaOS-Varianten), die Server-seitig
sowohl 'password' als auch 'keyboard-interactive' anbieten, fuehrte
keyboard-interactive zu einem stillen Fehlschlag ganz ohne sichtbaren
Prompt im Browser-Terminal (RFC 4256 erlaubt eine erste Runde ganz ohne
Prompts -- fuer den Nutzer unsichtbar -- gefolgt von sofortigem
Scheitern statt einer zweiten Runde mit dem eigentlichen Passwort-
Prompt). Das erklaerte exakt das gemeldete Symptom: kein Prompt, direkt
'Zugangsdaten falsch'.

'password' wird jetzt bevorzugt, wenn der Server es ueberhaupt anbietet
(Ende-zu-Ende gegen einen echten Host verifiziert: Host-Key-Bestaetigung
-> Passwort-Prompt -> erfolgreicher Login, keine Regression). Die
keyboard-interactive-Handler-Funktion wurde zusaetzlich gehaertet:
title/instructions werden jetzt mit angezeigt (manche Geraete legen den
Hinweistext dort statt in prompt_list ab) und leere prompt_text-Strings
bekommen einen Passwort-Fallback-Text, falls keyboard-interactive doch
der einzig angebotene Weg ist.
2026-08-13 22:30:41 +02:00
alientim e6f9a7aee0 KRITISCH: install.sh brach ohne TTY sofort und lautlos ab (clear + set -e)
"clear" schlägt ohne TERM/Pseudo-Terminal mit Exit-Code 1 fehl ("TERM
environment variable not set") -- z.B. genau dann, wenn install.sh (wie von
update.sh vorgesehen) per SSH exec_command ohne pty aufgerufen wird. Mit
"set -e" hat das die GESAMTE Installation sofort an Zeile 2 abgebrochen,
noch bevor auch nur ein apt-Paket installiert oder eine einzige Zeile ins
Install-Log geschrieben wurde -- kein Fehlertext, kein Hinweis, einfach
Stille. Live reproduziert: update.sh lud das Release-Paket korrekt herunter
und entpackte es, install.sh selbst tat aber schlicht nichts.

Behoben: "clear" ist jetzt bewusst nie fatal (2>/dev/null || true) -- rein
kosmetisch, hat keinen Einfluss auf die eigentliche Installation.
2026-08-13 21:40:15 +02:00
alientim 8fc9ce7b27 create_db.py/create_admin.py: frisches Setup auf den vollständig migrierten Schema-Stand bringen
Ein wirklich frisches Setup (create_db.py auf einer neuen Installation)
erzeugte bislang nur den ALLERERSTEN Schema-Stand aus der Projektgeschichte
-- alle seither per _ensure_schema()-Migration nachgerüsteten Spalten und
Tabellen fehlten komplett, bis diese Migration beim ersten App-Start
erfolgreich durchläuft. Live auf einem frisch installierten Produktivsystem
ist genau das nicht passiert: "sqlite3.OperationalError: no such column:
deleted_at" beim Dashboard-Aufruf, weil devices/switches/credentials/
users/groups ohne die Papierkorb-Spalte angelegt wurden. Fehlten außerdem
komplett: dhcp_subnets, dhcp_manual_reservations, service_accounts,
ldap_group_mappings (LDAP/DHCP-Funktionen wären auf einer solchen frischen
Installation gar nicht nutzbar gewesen) sowie dhcp_option_defs.is_standard.

- create_db.py jetzt deckungsgleich mit dem tatsächlichen, vollständig
  migrierten Schema (per .schema-Dump von einer produktiv laufenden Instanz
  verifiziert) -- category/deleted_at auf credentials, deleted_at auf
  switches, credential_id/ssh_port/deleted_at auf devices, deleted_at auf
  users/groups, is_standard auf dhcp_option_defs, plus die vier fehlenden
  Tabellen.
- POE_DB_PATH → TESM_DB_PATH in create_db.py UND create_admin.py (weiteres
  übersehenes Namensrelikt aus der TESM-Umbenennung), Docstring von
  create_admin.py aktualisiert.
- Live verifiziert: komplett frische DB erzeugt, App auf separatem
  Testport dagegen gestartet, alle wesentlichen Routen (Dashboard, Geräte,
  Switche, Zugangsdaten, Wartung, Papierkorb, Benutzer, Gruppen, alle
  Einstellungsseiten, Logs, Konto) liefern 200 ohne einen einzigen Fehler
  im App-Log -- eine frische Installation hängt jetzt an keiner Stelle mehr
  von der Laufzeit-Migration ab, um überhaupt benutzbar zu sein.
2026-08-13 20:53:31 +02:00
alientim 89682618f7 update.sh: auch /srv/poe_manager + poe_web.service (Vorgänger-Flask-App) abschalten
Ergänzt die bereits behandelte ursprüngliche Bash-only-Generation
(WiS/Aruba-PoE: rpi-check.service + ips.list) um die dazwischenliegende
Generation -- die Flask-App "PoE Manager" unter /srv/poe_manager mit
poe_web.service, der Vorgänger von TESM in diesem Repo vor der Umbenennung.
update.sh erkennt und sichert jetzt beide möglichen alten Installationsorte
(/srv/tesm, /srv/poe_manager) unabhängig voneinander nach
<Verzeichnis>-backup-<Zeitstempel> und stoppt/deaktiviert alle drei
möglichen Dienst-Generationen. README entsprechend erweitert.
2026-08-13 19:39:51 +02:00
alientim 31404266f3 update.sh: Release-Asset-Download statt git clone, ursprüngliches Bash-Setup mit abschalten
- update.sh lädt jetzt ein fertig gepacktes Gitea-Release-Asset
  (tesm-<tag>.tar.gz, Default "latest") direkt per curl herunter und
  entpackt es -- kein "git clone" bzw. Git auf dem Zielsystem mehr nötig.
- Stoppt/deaktiviert zusätzlich zu den aktuellen TESM-Diensten auch das
  ursprüngliche, noch ältere reine Bash-Setup (rpi-check.service,
  rpi-check-restart.service/.timer -- der Vorgänger von TESM ganz ohne
  Datenbank/Web-UI, statische ips.list statt generate_ips.py), falls auf
  dem Zielsystem noch vorhanden.
- README entsprechend aktualisiert, inkl. kurzer Anleitung zum Paketieren
  eines neuen Release (versionierter Tag + bewegliches "latest"-Tag).
2026-08-13 19:34:58 +02:00
alientim bfb58b62e9 Update/Reinstall-Skript für frische Release-Installationen (update.sh)
Ergänzt install.sh (das ein bestehendes Checkout voraussetzt) um einen Weg,
eine bisher ad-hoc gepflegte Installation (z.B. per manuellem Datei-Upload
während der Entwicklung) durch eine frische, aus einem Git-Tag geklonte
Installation zu ersetzen:
- Stoppt/deaktiviert tesm.service, tesm-check.service,
  tesm-check-restart.timer.
- Verschiebt das bestehende /srv/tesm NICHT gelöscht, sondern nach
  /srv/tesm-backup-<Zeitstempel> -- bleibt als Rückfalloption und
  Datenquelle für einen späteren Export/Import vollständig erhalten.
- Klont den angegebenen Release-Tag frisch und führt darin install.sh aus.
- Weist sowohl vor dem Start als auch am Ende deutlich darauf hin, dass das
  Ergebnis eine FRISCHE Installation ohne die Daten der alten Instanz ist --
  vorher exportieren (Einstellungen -> Im-/Export) oder danach alles neu
  einrichten.

README um den entsprechenden Abschnitt ergänzt.
2026-08-13 19:24:59 +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
alientimandClaude Sonnet 5 49634da0d9 Aufräumen: totes/unbenutztes entfernt, Beispiele verallgemeinert
- create_user.py entfernt: legte nur Nicht-Admin-Benutzer per CLI an,
  vollständig durch die Web-UI (Einstellungen -> Benutzer, unterstützt
  zusätzlich Vor-/Nachname und Gruppenzuordnung) abgedeckt und von
  nirgends (install.sh, README, systemd-Units) referenziert
- Ungenutzten Flask-Import send_from_directory entfernt (nie
  aufgerufen)
- Drei ungenutzte CSS-Regeln entfernt (in keinem Template/JS mehr
  referenziert): .btn-ghost, .dc-status (Karten-Layout hat kein
  .dc-status-Element mehr), .nav-divider
- Platzhalter für die manuelle DHCP-Reservierung nutzte als einziger
  im ganzen Projekt "192.168.80.x" statt der überall sonst
  verwendeten generischen Beispiel-Range "192.168.1.x" -- war ein
  Rest aus dem Testen gegen die konkrete Test-VM, jetzt vereinheitlicht
- Zwei nicht versionierte, lokale Log-Dateien (mini.log, server.log)
  aus dem Projektverzeichnis entfernt

Systematisch geprüft und dabei nichts Weiteres gefunden: alle 16
Templates werden tatsächlich gerendert, alle Imports (bis auf den
einen oben) werden verwendet, keine toten Hilfsfunktionen in app.py
(die drei Kandidaten aus einer AST-Analyse -- __getattr__,
avatar_url, display_name -- sind über Jinja-Attributzugriff bzw. als
Python-Dunder-Methode tatsächlich in Gebrauch), requirements.txt
enthält nur echte direkte + transitive Abhängigkeiten.

Live verifiziert: alle 14 Hauptseiten liefern nach dem Deploy weiterhin
HTTP 200, keine Fehler im journalctl-Log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 13:50:22 +02:00
alientimandClaude Sonnet 5 7b30ebce7b Logging & Log-Rotation, Zeitzone, Dashboard-UX, Design-Politur
Logging-Infrastruktur (Logs können nach einem Import mit vielen Geräten
schnell vollaufen):
- Vier eigene, rotierte Log-Dateien statt allem in /var/log vermischt:
  Live (poe.sh, jetzt eine einzige durchgehend fortgeschriebene Datei
  statt vorher ein neues rpi-<timestamp>.log pro Dienst-Neustart),
  Änderungen (Spiegel der audit_log-Tabelle), App (Flask-Fehler/
  Warnungen, vorher nur im journalctl-Output), Kea-DHCP (eigene
  Logging-Konfiguration in der generierten kea-dhcp4.conf)
- Kea-DHCP-Log bewusst NICHT im gemeinsamen Verzeichnis: Keas
  AppArmor-Profil erlaubt Schreibzugriff exklusiv auf
  /var/log/kea/kea-dhcp4.log (kein Verzeichnis-Wildcard) -- live als
  "DENIED ... mknod ... .lock" reproduziert; die App nutzt Keas eigenen,
  bereits erlaubten Pfad statt das AppArmor-Profil eines Systemdienstes
  aufzuweichen. logrotate deckt trotzdem beide Verzeichnisse mit einer
  Regel ab
- Neue Log-Seite "Kea-DHCP" (Logs-Bereich, eigenes Recht logs_kea.view)
- Rotation (Intervall täglich/wöchentlich/monatlich, Aufbewahrung als
  Anzahl Rotationen) unter Systemeinstellungen → Logs einstellbar,
  generiert /etc/logrotate.d/poe-manager; copytruncate, da keiner der
  drei schreibenden Prozesse ein Reopen-Signal implementiert
- Einmalige Migration räumt alte, verwaiste rpi-*.log-Dateien auf

Zeitzone (Systemeinstellungen, mit Hostname in einer Kachel "Host"):
setzt die System-Zeitzone per timedatectl, damit Logs/Änderungsverlauf
die tatsächliche lokale Zeit statt UTC zeigen. time.tzset() direkt nach
dem Setzen lässt die bereits laufende poe_web.service-Instanz die neue
Zeitzone sofort übernehmen, ohne Neustart.

Systemeinstellungen neu sortiert: Host + Netzwerkeinstellungen (beide
Host-Konfiguration) nebeneinander, dann Logs, dann Prüfintervall.

Netzwerkeinstellungen: DNS-Anzeige zeigte bisher den lokalen
systemd-resolved-Stub (127.0.0.53) aus /etc/resolv.conf statt der
echten Server -- jetzt über "resolvectl dns <interface>", inkl. Fix
für eine falsch geparste IPv6-Fortsetzungszeile ohne Label, und
gefiltert auf IPv4 (diese Seite verwaltet nur IPv4-Konfiguration, per
IPv6-Router-Advertisement gelernte Server sind hier irreführend).

Dashboard: die vier Stat-Kacheln (Online/Offline/Deaktiviert/Gesamt)
sind jetzt klickbare Filter (blenden alle anderen Abschnitte aus, Klick
auf die aktive Kachel oder "Gesamt" hebt den Filter auf); jeder
Abschnitt lässt sich unabhängig davon per Klick auf seinen Titel ein-/
ausklappen. Beide Zustände bleiben über die automatischen Live-Updates
hinweg erhalten.

Design-Politur:
- Hauptinhaltsbereich nutzt jetzt die volle verfügbare Breite (kein
  Bootstrap-typisches festes max-width mehr) -- Modals bleiben bewusst
  bei ihrer kompakten, festen Breite
  Akzentfarbe exakt auf das Orange aus dem WiS-Logo abgestimmt
  (#ff7100, per Pixel-Sampling ermittelt) statt einer nur ähnlichen,
  frei gewählten Nuance
- Abmelden führt zurück auf das (auch ohne Login einsehbare) Dashboard
  statt auf die Login-Seite

Live gegen die echte Test-VM verifiziert: alle vier Logs werden
geschrieben, logrotate-Konfiguration korrekt generiert, ein
Zeitzonenwechsel (UTC → Europe/Berlin) wirkte sofort, DNS-Anzeige zeigt
jetzt die echten IPv4-Server (1.1.1.1, 8.8.8.8) statt Stub/IPv6,
Dashboard-Filter/-Collapse und Logout-Redirect per Playwright bestätigt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 13:30:56 +02:00
alientimandClaude Sonnet 5 d525ef2e0a poe.sh: parallele Geräte-Prüfung + echte PoE-Erfolgskontrolle
Nach einem echten Import (46 Geräte, 8 Switche aus dem Altsystem)
zeigten sich zwei Probleme in der Hintergrund-Prüfschleife:

- Geräte wurden strikt sequentiell geprüft (eine einzige "while read"-
  Schleife) -- ein einzelnes ausgefallenes Gerät mit ebenfalls nicht
  erreichbarem Switch (bis zu 5s expect-Timeout je Verbindungsschritt)
  verzögerte die Prüfung aller nachfolgenden Geräte. Jede Geräte-Prüfung
  läuft jetzt als eigener Hintergrund-Job (& + wait), über Process
  Substitution statt einer Pipe, damit wait die Jobs auch tatsächlich
  erfasst (bei "cmd | while ...; done" liefe die Schleife sonst in
  einer eigenen Subshell).
- disable_poe()/enable_poe() haben unabhängig vom tatsächlichen Ergebnis
  der SSH/expect-Sitzung immer wie ein Erfolg gewirkt -- lief die
  Verbindung in einen Timeout (Switch nicht erreichbar, falsche
  Zugangsdaten, falscher Port), endete das expect-Skript trotzdem mit
  Exit-Code 0, poe.sh loggte fälschlich "PoE deaktiviert"/"PoE
  aktiviert", obwohl am Switch nichts passiert war. Jeder kritische
  expect-Schritt hat jetzt einen eigenen timeout-Zweig mit explizitem
  "exit 1"; poe.sh wertet den Rückgabewert aus und loggt bei einem
  nicht erreichbaren/ablehnenden Switch stattdessen klar, dass die
  PoE-Aktion fehlgeschlagen ist. Gilt für die Prüfschleife, den
  manuellen Neustart (poe.sh restart <mac>) und damit auch den
  Dashboard-Neustart-Button.

Live verifiziert: alle 46 Geräte innerhalb von ~2s geprüft (statt
sequentiell), alle 43 offline erkannten Geräte mit Switch-Zuordnung
zeigten korrekt die neue Fehlermeldung, da die realen Switch-IPs vom
Test-Host aus nicht erreichbar sind -- vorher hätte das fälschlich als
43x "PoE deaktiviert/aktiviert" geloggt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 11:59:52 +02:00
alientimandClaude Sonnet 5 3b2a5fc76c Wartung: Online-Watchdog nach Neustart, Status-Reset, Zugangsdaten-Spalte
- Nach einem ausgelösten SSH-Neustart blieb der Job-Status bisher für
  immer auf "Neustart ausgelöst" stehen -- ergänzt um einen Ping-
  Watchdog (gleiche Invocation wie das bestehende Monitoring in
  poe.sh: "ping -c 1 -W 2"), der nach einer kurzen Schonfrist (15s)
  regelmäßig prüft, bis das Gerät wieder antwortet (Timeout 5min).
  Status wechselt dann sichtbar auf "wieder online" bzw. auf einen
  Fehler bei Zeitüberschreitung.
- Ein abgeschlossener ERFOLGREICHER Job wird bei jedem (erneuten) Laden
  der Wartungsseite zurückgesetzt (wieder "Noch keine Aktion") -- ein
  Erfolg ist nur relevant, solange man ihn live mitverfolgt. Fehler
  bleiben bewusst bis zur nächsten Aktion sichtbar, um sie nicht durch
  einen einfachen Reload zu übersehen.
- Clients-Tabelle zeigt jetzt wie die Switche-Tabelle eine
  "Zugangsdaten"-Spalte (Name + Username oder "— keine —").

Live gegen das dedizierte Testsystem verifiziert: zwei echte Neustarts,
beide per uptime -s bestätigt (kein False-Positive-Ping gegen die noch
nicht heruntergefahrene alte Instanz), Status korrekt von "wartet auf
Online" auf "wieder online" gewechselt; Status-Reset beim Seitenaufruf
ebenfalls live bestätigt (sichtbar vor, leer nach einem GET /maintenance).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 11:35:51 +02:00
alientimandClaude Sonnet 5 8772f7777b Wartung: SSH-Bulk-Update & -Neustart für Linux-Clients
Neue Seite "Wartung" (Geräte-Bereich, eigenes Rechtepaar
maintenance.view/maintenance.edit), komplett getrennt vom PoE-Neustart
auf dem Dashboard:

- Dashboard-Neustart bleibt ausschließlich PoE-Reset über den Switch
- Wartung läuft immer per SSH direkt auf dem Gerät, unabhängig von
  jeder Switch-Zuordnung -- nur Geräte mit SSH-Zugangsdaten der
  Kategorie "Linux-Client" erscheinen dort (Windows/PowerShell folgt
  als eigener, separater Schritt -- kein Testsystem dafür verfügbar)

Backend:
- _run_ssh_command()/_run_ssh_reboot(): nicht-interaktive SSH-Ausführung
  mit paramiko.RejectPolicy() gegen dieselbe known_hosts-Datei wie das
  interaktive Browser-Terminal -- ein Host muss vorher einmal per
  "Verbindung testen" bestätigt worden sein, da hier (anders als im
  Terminal) niemand eine "yes/no"-Rückfrage beantworten kann
- _apt_upgrade_command(): "apt update && apt upgrade -y" nicht-
  interaktiv mit DEBIAN_FRONTEND=noninteractive und
  --force-confdef/--force-confold, damit ein Paket-Postinst-Skript
  nicht auf eine nie kommende Config-Rückfrage wartet
- Reboot behandelt einen abrupten Verbindungsabbruch NACH dem Kommando
  als Erfolg (die Maschine fährt herunter, bevor sie antworten kann);
  nur ein Fehler VOR dem Reboot-Kommando zählt als echter Fehler
- Job-Status lebt im Prozessspeicher (wie das SSH-Terminal), Live-
  Anzeige per Polling (/maintenance/status)

Live gegen ein dediziertes Testsystem (getrennt vom gemeinsam
genutzten App-Host, um dort kein echtes apt upgrade auszulösen)
verifiziert: echtes "apt update && apt upgrade" inkl. Kernel-/systemd-/
netplan-Paketen lief vollständig durch, anschließender SSH-Neustart hat
das System nachweislich neu gestartet (uptime nach dem Job bestätigt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 11:12:07 +02:00
alientimandClaude Sonnet 5 63ca7782ba Geräte: SSH-Zugangsdaten mit Kategorien, PoE-Neustart nur wenn möglich
- credentials: neue Spalte 'category' (Switch/Linux-Client/
  Windows-Client/Router), Grundlage für die geplante SSH-basierte
  Wartungsfunktion (Bulk-Updates); Löschschutz prüft jetzt zusätzlich
  devices.credential_id, nicht mehr nur switches.credential_id
- devices: optionale SSH-Zugangsdaten + SSH-Port je Gerät, inkl.
  "Verbindung testen"-Terminal wie bei Switches
- Dashboard: PoE-Neustart-Button wird für Geräte ohne zugewiesenen
  Switch/Port komplett ausgeblendet (nicht nur ausgegraut), da für
  diese kein PoE-Reset über poe.sh möglich ist; Button bleibt
  ausschließlich für den PoE-Neustart über den Switch zuständig
- Schließen-Button im Geräte-Modal immer rechts, unabhängig davon
  welche Aktions-Buttons gerade sichtbar sind
- "Port" im Geräte-Formular zu "Switchport" umbenannt und von
  "SSH-Port" klar abgegrenzt (unterschiedliche Konzepte: physischer
  Switch-Port fürs PoE vs. TCP-Port für SSH)
- .btn:disabled greift jetzt echtes Grau statt nur reduzierter
  Deckkraft der jeweiligen Button-Farbe

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 10:34:30 +02:00
alientim d3cf78592f Fix: 'host-name'-Option kollidierte mit dem nativen Reservierungs-Hostname
Der Nutzer fragte zu Recht nach, ob eine Reservierung mit gleichzeitig
gesetztem hostname-Feld (aus dem Namen abgeleitet, z.B. 'unraid') UND
einem option-data-Override fuer 'host-name' (Code 12, z.B. 'test') so
beabsichtigt ist. Laut Kea-Dokumentation beantwortet Kea die Host-Name-
Anfrage eines Clients bei einer Reservierung automatisch mit deren
eigenem hostname-Feld -- der zusaetzliche Options-Override fuer dieselbe
Option war eine unbeabsichtigte Dopplung/Quelle fuer Widersprueche, keine
zusaetzliche Funktion.

'host-name' aus den 56 verbleibenden Standard-Optionen entfernt (war eine
von 57), neue Migration v3 loescht eine bereits geseedete/gesetzte
host-name-Zeile samt Wert aus bestehenden Installationen nach. Live auf
der Test-VM verifiziert: Option ist verschwunden, die 'unraid'-Reservierung
enthaelt jetzt nur noch hw-address/ip-address/hostname ohne option-data,
kea-dhcp4 -t weiterhin erfolgreich.
2026-08-11 16:53:33 +02:00
alientim 255308f81b App-weite Warnung bei ungespeicherten Aenderungen + Options-UI-Fix + Cache-Busting
1. Generischer Ja/Nein-Dialog bei ungespeicherten Aenderungen (kein Opt-in
   pro Seite noetig): initUnsavedChangesGuard() erkennt automatisch jedes
   Formular mit echten Eingabefeldern, das seit dem Laden der Seite
   veraendert wurde, und fragt beim Klick auf einen Link (z.B. Sidebar-
   Navigation), ob zuerst gespeichert werden soll -- eigener Dialog
   (confirmSaveDiscard) statt des bestehenden confirmAction (dort ist
   "Abbrechen" = Bleiben, hier gibt es bewusst nur die zwei angefragten
   Optionen: speichern oder verwerfen, beide verlassen die Seite).
   "Ja, speichern" sendet das Formular ganz normal ab (echter POST) und
   merkt sich das eigentliche Ziel in sessionStorage, um danach automatisch
   dorthin weiterzuleiten.

   Nachtrag noch in dieser Session gefixt: ein normales Speichern ueber den
   Seiten-eigenen Button loeste zusaetzlich die native Browser-Warnung
   (beforeunload) aus, obwohl die Aenderung bereits gespeichert wurde --
   navigatingAway-Flag wird jetzt bei jedem Submit eines beobachteten
   Formulars (eigener Button ODER eigener Ja/Nein-Dialog) gesetzt, die
   generische Browser-Warnung erscheint dadurch nur noch bei tatsaechlich
   ungefragter Navigation (Tab schliessen, Reload, Adresszeile). Live per
   Playwright in drei Szenarien verifiziert (direktes Speichern, Ja via
   eigenem Dialog, Nein via eigenem Dialog) -- kein Browser-Popup in den
   ersten beiden Faellen, korrektes Redirect zum urspruenglichen Ziel nach
   "Ja".

2. DHCP-Options-Dropdown (Reservierungs-Dialog) verschiebt ein neu
   ausgewaehltes Options-Feld jetzt direkt unter das Dropdown (in einen
   .poe-added-options-Container) statt es nur an seiner urspruenglichen
   Stelle in der bis zu 57 Eintraege langen Liste einzublenden -- lag dort
   oft ausserhalb des sichtbaren Modal-Ausschnitts und wirkte, als waere
   nichts passiert. Bereits gesetzte Overrides stehen von Anfang an im
   selben Container, weitere haengen sich beim Auswaehlen unten an.

3. Cache-Busting fuer app.js/style.css (asset_url()-Helper, haengt die
   Aenderungszeit der Datei als ?v=... an) -- ein im Browser gecachtes,
   veraltetes app.js konnte nach einem Update sonst den Eindruck erwecken,
   eine neue Funktion wuerde "nicht funktionieren", obwohl der Code auf dem
   Server laengst aktuell war.

Alle drei live auf der Test-VM deployt und per Playwright verifiziert,
keine Fehler im journalctl-Log. (Testartefakt aufgeraeumt: der echte
Host-Hostname wurde durch die Playwright-Tests versehentlich auf
"directsave-test"/"navsave-test" geaendert und wieder auf POETEST
zurueckgesetzt.)
2026-08-11 16:39:03 +02:00
alientim 1a0b55ed44 DHCP: manuelle Reservierungen, Options-Schutz+Dropdown-UI, Lease-Countdown-Fix
1. Manuelle Reservierungen (dhcp_manual_reservations) fuer Geraete, die
   nicht als Client in dieser App gepflegt werden -- bewusst eine eigene,
   explizite Aktion (MAC/IP/Name), damit kein externes Geraet unueberwacht
   automatisch eine feste IP bekommt. _dhcp_reservation_candidates fuehrt
   automatische (aus devices) und manuelle Reservierungen jetzt zusammen,
   case-insensitiv nach MAC dedupliziert (automatisch gewinnt bei Konflikt).
   Automatische Reservierungen sind in der Tabelle nicht loeschbar
   (Schloss-Symbol statt Button, sie entstehen/verschwinden mit den
   Client-Stammdaten), manuelle schon. Live verifiziert: Hinzufuegen,
   doppelte MAC ablehnen, ungueltige MAC ablehnen, generierte Kea-Config
   mit gemischten Auto-/manuellen Reservierungen besteht kea-dhcp4 -t.

2. Standard-DHCP-Optionen um 10 weitere verifizierte Optionen erweitert
   (u.a. host-name/12 -- Kea-Name mit Bindestrich, NICHT "hostname", live
   gegen echtes Kea geprueft) -- jetzt 57 insgesamt. Standard-Optionen sind
   nicht mehr loeschbar (Backend lehnt es ab, UI zeigt ein Schloss-Symbol
   statt Loeschen-Button) -- vorher liess sich eine vorbefuellte Options-
   Definition versehentlich entfernen.

3. Options-Tabellen (global UND pro Reservierung) zeigen jetzt nur noch
   tatsaechlich genutzte Options -- eigene immer, Standard-Optionen nur mit
   gesetztem Wert. Weitere Standard-Optionen kommen ueber ein Dropdown
   "+ Option hinzufuegen" dazu statt permanent alle 57 als leere Felder zu
   zeigen. Das neu eingeblendete Feld wird automatisch in den sichtbaren
   Bereich gescrollt und fokussiert (bei bis zu 57 moeglichen Optionen kann
   das Feld sonst weit unterhalb des sichtbaren Modal-Ausschnitts liegen
   und es wirkt so, als waere nichts passiert).

4. Lease-Countdown-Fix: die Ablaufzeit wird jetzt explizit als UTC
   ausgewiesen (Kea arbeitet intern in UTC-Epoch) und zusaetzlich als live
   tickender Countdown angezeigt (gleiches Prinzip wie der bestehende
   "Naechste Pruefung"-Timer). Vorher wirkte eine reine Absolutzeit auf der
   nicht selbst aktualisierenden Seite bei den kurzen Standard-Lease-Zeiten
   (600s) schnell "schon abgelaufen", sobald etwas Zeit vergangen war, bis
   man tatsaechlich hinschaut -- live im echten Betrieb genau als dieses
   Missverstaendnis beobachtet und anhand der rohen Kea-CSV verifiziert,
   dass die richtige Spalte (expire) korrekt gelesen wird.

5. UI-Feinschliff: "In Datei schreiben" bei den Reservierungen heisst jetzt
   "Speichern" und ist der primaere (orange) Button, "Reservierung
   hinzufuegen" sekundaer -- das Hinzufuegen legt nur einen Datensatz an,
   erst "Speichern" schreibt die tatsaechlich wirksame Kea-Config.

Alles live auf der Test-VM deployt und verifiziert (Playwright fuer die
Dropdown-Sichtbarkeit/Fokus-Interaktion, curl fuer die Backend-Validierung,
kea-dhcp4 -t fuer die generierte Konfiguration), keine Fehler im
journalctl-Log.
2026-08-11 15:52:52 +02:00
alientim 9885a579ea DHCP: Subnetze deaktivierbar + 47 verifizierte Standard-Optionen
1. Subnetze koennen einzeln deaktiviert werden (enabled-Spalte), ohne sie
   loeschen zu muessen -- werden dann bei "In Datei schreiben" und
   "Aktivieren & (neu) starten" komplett uebersprungen. Bewusst nicht als
   "ungueltig" gewertet (kein Fehler/keine Warnung), da eine deaktivierte
   Zeile absichtlich kein echtes Netz braucht. Toggle-Button in der
   Subnetz-Tabelle, deaktivierte Zeilen abgeblendet mit "Deaktiviert"-Pill.

2. 47 kuratierte Standard-DHCP-Optionen vorbefuellt (analog den
   "Predefined Options" eines Windows-DHCP-Servers) -- Router/DNS-Server/
   Domain-Name/Lease-Zeiten ausgenommen, dafuer gibt es bereits eigene
   Felder. Alle Namen live gegen eine echte Kea-2.4.1-Instanz verifiziert:
   jede der 49 Kandidaten-Optionen einzeln per kea-dhcp4 -t getestet, 47
   akzeptiert (2 falsch geratene Namen verworfen: nis-domain-name statt
   nis-domain, nisplus-domain statt nisplus-domain-name).

   Wichtig fuer Kea-Kompatibilitaet: Standard-Optionen bekommen KEIN
   eigenes option-def in der generierten Konfiguration (neue is_standard-
   Spalte in dhcp_option_defs) -- Kea kennt sie bereits nativ, eine
   Neudefinition haette die eingebaute dupliziert. Nur echte eigene/
   herstellerspezifische Options bekommen weiterhin ein option-def.
   Live verifiziert: option-def bleibt bei gesetztem Standard-Options-Wert
   leer, option-data enthaelt den Wert korrekt, kea-dhcp4 -t besteht,
   echter Dienstneustart mit der Config erfolgreich.

   Einmalige Migration (Guard wie bei den anderen Schema-Migrationen) --
   ein vom Admin bewusst geloeschter Standard-Eintrag wird nach einem
   Neustart nicht stillschweigend wieder angelegt.

Ausserdem in dieser Session zuvor gefixt (siehe vorherige Commits): der
falsche Kea-Schluessel lease-file->name, der jeden Dienststart blockierte,
sowie der 'Jetzt pruefen'-Button, der den Topbar-Countdown nur auf dem
Dashboard aktualisierte.
2026-08-11 15:06:55 +02:00
alientim 182457629d Fix: 'Jetzt pruefen' aktualisierte Topbar-Countdown nur auf dem Dashboard
Der Klick loeste den Neustart von rpi-check.service und damit einen
echten, sofortigen Pruef-Durchlauf immer korrekt aus -- nur die
Topbar-Anzeige 'Naechste Pruefung in Xs' wurde ausserhalb des Dashboards
nie aktualisiert, da nur index.html einen Listener fuer das
poe:check-triggered-Event registrierte. Auf allen anderen Seiten (DHCP,
Switches, Settings, ...) lief der Countdown deshalb sichtbar mit dem alten
Zeitpunkt weiter, obwohl im Hintergrund laengst neu geprueft wurde.

Fix (klein gehalten, kein neuer Endpunkt): run_check_now() wartet kurz auf
den ersten Log-Eintrag des frisch gestarteten Dienstes und liefert den
neuen Zeitpunkt direkt in seiner bestehenden JSON-Antwort mit. app.js
schreibt diesen Wert danach unabhaengig von der aktuellen Seite ins
global-timer-pill-Element -- der laufende Countdown liest seine Werte
ohnehin bei jedem Tick frisch aus dem DOM.

Live auf der Test-VM verifiziert (mehrere echte Durchlaeufe ueber
/check/run-now, frischer last_run_epoch_ms in der Antwort, keine Fehler
im journalctl-Log).
2026-08-11 14:51:07 +02:00
alientim bf9b816fa2 DHCP: Mehrfach-Subnetze, aktive Leases-Anzeige, Reboot-Persistenz
1. Mehrere gleichzeitige DHCP-Subnetze statt einer einzelnen globalen Range
   -- ein Host kann mehrere IPs/Interfaces mit jeweils eigenem Netz haben,
   fuer die alle DHCP angeboten werden soll:
   - Neue Tabelle dhcp_subnets (interface, range_start/end, optionales
     gateway/dns je Zeile) statt der bisherigen dhcp_interface/_range_*/
     _gateway/_dns-Settings-Keys. Domain, Lease-Zeiten und Ausgabepfad
     bleiben global (Kea-weit gueltig).
   - Einmalige Migration: eine bestehende globale Einzel-Range wird beim
     ersten Start automatisch in eine erste Subnetz-Zeile ueberfuehrt,
     statt eine funktionierende Konfiguration beim Upgrade zu verlieren
     (live verifiziert: bestehende 192.168.80.220-230-Range korrekt
     uebernommen).
   - Neue Routen add/edit/delete_dhcp_subnet, jede mit derselben
     Sicherheitsregel wie zuvor: es muss eine physische IP im gewuenschten
     Bereich vorhanden sein (_dhcp_matching_network je Subnetz-Zeile),
     sonst wird die Aktion abgelehnt. _detect_interface_network wurde dafuer
     in eine neue _detect_interface_networks (alle IPv4-Adressen eines
     Interfaces, nicht nur die erste -- ein Interface kann mehrere IPs
     tragen) plus einen duennen Wrapper fuer die primaere Adresse aufgeteilt.
   - _render_kea_config generiert jetzt einen eigenen subnet4-Block je
     Subnetz (eigenes Gateway/DNS-option-data), _dhcp_reservation_candidates
     ordnet jedes Geraet anhand seiner IP dem richtigen Subnetz zu.
   - "In Datei schreiben" ueberspringt inzwischen ungueltig gewordene
     Subnetze einzeln (mit Warnung, welche) statt die ganze Aktion
     abzubrechen; "Aktivieren & (neu) starten" verweigert den Start, falls
     kein einziges gueltiges Subnetz mehr existiert.
   - Live auf der Test-VM verifiziert: Anlegen/Bearbeiten/Loeschen inkl.
     Ablehnung ungueltiger Ranges, generierte Kea-Config mit zwei
     subnet4-Bloecken korrekt (Interface, Subnet-CIDR, Pools je Zeile).

2. Aktive Leases direkt aus Kea sichtbar (settings_dhcp.html, neue Karte)
   -- macht Clients OHNE eigene Reservierung sichtbar, die sich einfach
   eine freie IP aus dem Pool genommen haben, statt nur die (unvollstaendige)
   Reservierungsliste zu zeigen. Liest die memfile-Lease-CSV direkt
   (kein Kea-Control-Agent noetig), reserved-Flag durch Abgleich mit den
   bekannten Reservierungs-MACs. Live gegen eine echte (leere)
   kea-leases4.csv verifiziert.

3. DHCP-Dienst uebersteht einen Host-Neustart jetzt korrekt im zuletzt
   bewusst gewaehlten Zustand: "Stoppen" deaktiviert den Dienst zusaetzlich
   (nicht nur systemctl stop), sonst wuerde systemd ihn nach einem Neustart
   automatisch wieder hochfahren, obwohl der Admin ihn bewusst abgeschaltet
   hat. Dieselbe Ergaenzung im automatischen Netzwerkaenderungs-Stop (siehe
   vorheriger Commit) -- sonst koennte ein Host-Neustart vor der
   Range-Pruefung den Dienst trotzdem mit einer ggf. falschen Konfiguration
   wieder starten.
2026-08-11 14:42:27 +02:00
alientim efda77f813 Fix: falscher Kea-Schluessel lease-file statt name blockierte jeden Dienststart
Live reproduziert: 'kea-dhcp4 -t' lehnte JEDE generierte Konfiguration mit
'got unexpected keyword "lease-file" in lease-database map' ab, sobald
mindestens ein Subnetz konfiguriert war -- der Dienst liess sich dadurch
nie starten, unabhaengig von der Anzahl/Gueltigkeit der Subnetze selbst.
Kea nennt den Datei-Pfad des memfile-Lease-Backends 'name', nicht
'lease-file'. Nach dem Fix: 'kea-dhcp4 -t' besteht, Dienststart auf der
Test-VM mit einem echten Subnetz verifiziert (DHCP4_STARTED, Dienst aktiv).
2026-08-11 14:42:07 +02:00
alientim 9660258940 Fix: DHCP-Range wurde nie gegen das tatsaechliche Netz geprueft
Auf der Test-VM liess sich der Dienst mit einer gespeicherten Range
(192.168.81.203-212) starten, obwohl das erkannte Netz des Interfaces
192.168.80.0/24 war -- die Range gehoerte also zu einem komplett anderen
Subnetz. _dhcp_config_is_complete() prophfte bisher nur, ob Range Start/
Ende ueberhaupt ausgefuellt waren, nie ob sie zum echten Netz passen.

Neue Funktion _dhcp_matching_network(interface, start, end) validiert die
Range jetzt an drei Stellen: beim Speichern (save_dhcp_config), beim
Schreiben der Kea-Datei (write_dhcp_file) und beim Starten des Dienstes
(dhcp_service_action=enable_restart) -- jede der drei Aktionen wird mit
einer genauen Fehlermeldung abgelehnt, statt eine zum Netz nicht passende
Range durchzulassen.

Die Pruefung beruecksichtigt dabei ALLE IPv4-Adressen des Interfaces, nicht
nur die erste -- ein Interface kann mehrere IPs/Subnetze gleichzeitig
tragen (Alias-IPs). Dafuer wurde _detect_interface_network() in eine neue
_detect_interface_networks() (Mehrzahl, alle Adressen) plus einen duennen
Wrapper fuer die primaere/erste Adresse aufgeteilt -- alle bisherigen
Aufrufer (Anzeige auf der DHCP-Seite, Host-Netzwerkeinstellungen) bleiben
unveraendert auf der primaeren Adresse.

Echte Mehrfach-Ranges gleichzeitig (mehrere Subnetze parallel, z.B. auf
verschiedenen Interfaces) sind bewusst nicht Teil dieses Fixes -- laut
Ruecksprache ein separater, groesserer Umbau (DB-Tabelle statt globaler
Range, mehrere subnet4-Bloecke in der Kea-Config, Listen-UI) und als
naechster Schritt vorgesehen.

Live auf der Test-VM verifiziert: falsche Range wird beim Speichern,
Schreiben und Starten korrekt abgelehnt, eine zum erkannten Netz passende
Range weiterhin akzeptiert -- keine Fehler im journalctl-Log.
2026-08-11 13:43:06 +02:00
alientim 357bb4caa5 Fix: iputils-ping fehlte in install.sh-Paketliste
Auf der Test-VM war 'ping' nicht vorhanden (Minimal-Ubuntu-Image liefert
es nicht immer vor) -- fuer die Erreichbarkeitspruefung der Geraete
(Pruefintervall / rpi-check-Dienst) zwingend noetig. Live auf dem
Testsystem nachinstalliert und verifiziert.
2026-08-11 13:30:45 +02:00
alientim 3195df0fd3 Fix: nginx blockierte SSH-Terminal-WebSocket + DHCP-Stop bei Netzwerkaenderung
Auf der frisch installierten Test-VM zeigten sich zwei weitere reale Probleme:

1. "Verbindung testen" im Switch-Modal blieb dauerhaft auf "Getrennt"
   haengen, sobald ueber nginx (Port 80) statt direkt gegen Flask (Port
   5000) zugegriffen wurde. Ursache: etc/nginx/sites-available/poe_manager
   reichte den "Upgrade"/"Connection"-Header fuer /ws/ssh_terminal nicht
   durch -- Flask/Werkzeug lehnte den WebSocket-Handshake dadurch mit
   400 Bad Request ab, statt mit 101 Switching Protocols zu antworten.
   Live reproduziert und verifiziert (curl-Handshake direkt vs. durch
   nginx). Alle bisherigen Terminal-Tests dieser Session liefen ausschliesslich
   direkt gegen Flask, nie durch nginx, daher bis jetzt unentdeckt.
   Fix: eigener location /ws/ Block mit Upgrade-Map (Standard-nginx-Pattern)
   und grosszuegigen Timeouts fuer die lang offene Terminal-Verbindung.

2. Der Kea-DHCP-Server lief nach einer Aenderung der Host-Netzwerkkonfiguration
   unveraendert weiter, obwohl die konfigurierte Range nach einem
   IP-/Subnetzwechsel nicht mehr zum neuen Netz passen muss. Neue Funktion
   _stop_dhcp_service_if_active() stoppt Kea jetzt sowohl beim Anwenden
   als auch beim Auto-Rollback einer Netzwerkaenderung -- bewusst nur
   stoppen statt automatisch neu zu starten, die Range muss der Admin
   aktiv auf der DHCP-Seite pruefen und den Dienst dort bewusst wieder
   starten (gleiche Philosophie wie beim Rest der DHCP-Dienststeuerung).

Beide Fixes live auf der Test-VM verifiziert (WebSocket-Handshake per
curl, Netzwerk-Reapply ohne Fehler im journalctl-Log trotz nicht
installiertem Kea).
2026-08-11 13:29:02 +02:00
alientim 2b5d9d1b48 Netzwerkeinstellungen: netplan/systemd-networkd als drittes Backend + DHCP-Interface-Default-Fix
Auf einer echten, frisch installierten Ubuntu-24.04-VM (192.168.80.114,
Standard-Setup, Netz per DHCP) zeigten sich zwei reale Bugs:

1. Systemeinstellungen > Netzwerkeinstellungen: 'Kein unterstütztes
   Backend erkannt' trotz laufendem Netz -- _detect_network_backend()
   kannte nur NetworkManager und dhcpcd, nicht netplan/systemd-networkd,
   den mit Abstand haeufigsten Fall auf einem normalen Ubuntu-Host.
   Neues drittes Backend 'netplan' ergaenzt: Erkennung ueber
   systemd-networkd + vorhandenes netplan-Binary, Moduserkennung ueber
   das 'dynamic'-Flag des Kernels auf der aktuellen Adresse, Anwenden
   ueber eine eigene Override-Datei (/etc/netplan/90-poe-manager.yaml,
   gewinnt alphabetisch gegen z.B. cloud-inits 50-*.yaml) plus
   Aufruf von netplan apply, Backup/Auto-Rollback wie bei den anderen
   Backends.
   Live verifiziert: Anwenden loeste echten DHCP-Lease-Renew aus, nach
   Ablauf der 45s-Bestaetigungsfrist wurde die Override-Datei automatisch
   wieder entfernt (Rollback funktioniert).

2. DHCP-Konfiguration: 'Keine IPv4-Adresse auf eth0 gefunden', obwohl
   das Interface-Dropdown korrekt 'enp1s0' zeigte -- DHCP_DEFAULTS hatte
   'eth0' hart kodiert (Annahme aus einer aelteren Testumgebung), was auf
   Systemen mit Predictable Network Interface Names schlicht nicht
   existiert. _load_dhcp_settings() korrigiert das jetzt: liegt der
   gespeicherte/Default-Wert nicht unter den tatsaechlich vorhandenen
   Schnittstellen, wird automatisch die erste echte vorgeschlagen.

Neue Abhaengigkeit PyYAML fuer das Schreiben der netplan-Override-Datei.
2026-08-11 13:04:43 +02:00
alientim 9e23243872 Fix: rsync fehlte in install.sh-Paketliste (Deploy-Schritt braucht es)
Live auf frischem Testsystem (Ubuntu 24.04, 192.168.80.114) verifiziert:
Skript brach beim Kopieren nach /srv/poe_manager mit 'rsync: command
not found' ab, weil rsync nirgends in der apt-get-install-Zeile stand.
2026-08-11 12:35:40 +02:00
alientimandClaude Sonnet 5 4d7433e832 Fix: SSH-Terminal-Login (No auth methods), kaputtes DOM in Bearbeiten-Modals
Quelltext-Huerde wieder entfernt (auf Wunsch) - siehe vorherigen Commit,
hier nur der Revert von initSourceProtection() und der README-Erwaehnung.

SSH-Terminal-Login war komplett kaputt (von dir gemeldet + Screenshot):
- Root Cause: das Init-Payload vom Browser enthielt nie ein Passwort,
  SSHClient.connect() bekam also weder Passwort noch Key noch Agent und
  scheiterte sofort mit "No authentication methods available" - noch
  bevor ueberhaupt eine interaktive Passwortabfrage moeglich gewesen
  waere (die High-Level-API erledigt Host-Key-Pruefung UND
  Authentifizierung in einem blockierenden Aufruf).
- Fix: Umstieg auf die Low-Level paramiko.Transport-API. Nach
  Host-Key-Bestaetigung wird aktiv erfragt, welche Auth-Methoden der
  Server anbietet (auth_none), und bei Bedarf interaktiv ueber das
  Browser-Terminal nach Passwort/keyboard-interactive-Prompts gefragt
  -- genau das Verhalten, das der bestehende Hinweistext im Modal schon
  immer versprach, aber nie tatsaechlich implementiert war.
  Host-Key-Verifikation dabei manuell nachgebaut (_verify_host_key_interactive)
  inkl. hartem Ablehnen bei GEAENDERTEM (nicht nur unbekanntem) Host-Key,
  wie ein echtes ssh-CLI bei einer moeglichen MITM-Situation.
- Waehrend der Live-Verifikation gegen ein echtes Geraet zwei weitere
  Bugs gefunden und gefixt: ws.receive() wirft in diesem Setup
  ConnectionClosed statt None zurueckzugeben (crashte
  _terminal_read_line unbehandelt -> "Invalid frame header" beim
  Client); _send_and_close() crashte ebenso, wenn der Client bereits weg
  war. Beide jetzt defensiv abgefangen.
- Live gegen ein echtes Zielgeraet verifiziert (Host-Key-Bestaetigung,
  Passwort-Prompt, erfolgreicher Login) sowie manuell von dir bestaetigt.

Kaputtes DOM in zwei Bearbeiten-Modals (von dir gemeldet: "Verbindung
testen" oeffnete beim Switch bearbeiten kein Fenster, obwohl es beim
Neuanlegen funktionierte):
- Root Cause: <div class="modal-overlay">...</div> stand direkt in
  <tbody> (nur <tr> ist dort gueltig). Browser "foster-parenten"
  ungueltigen Tbody-Inhalt aus der Tabelle heraus und zerreissen dabei
  teils die Eltern-Kind-Beziehung zwischen <form> und seinen Buttons --
  this.closest("form") lieferte dadurch null statt des Formulars.
  Betroffen: editSwitchModal (switches.html), deviceOptionsModal
  (settings_dhcp.html). Fix: beide Modal-Bloecke aus der Tabelle heraus
  in eine eigene Schleife direkt danach verschoben (gleiches Muster wie
  die bereits korrekten Neuanlegen-Modals).
- Per DOM-Inspektion verifiziert: this.closest("form") lieferte vorher
  null, danach das korrekte Formular fuer alle Zeilen; End-to-End-Test
  bestaetigt, dass sich das Terminal-Modal jetzt oeffnet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 12:00:22 +02:00
alientimandClaude Sonnet 5 6803929140 Hostname-Einstellung, DHCP-Range-Pflicht, Topbar-Ampel, Text-Straffung, Quelltext-Huerde
Hostname (Systemeinstellungen, neben Pruefintervall):
- hostnamectl set-hostname ueber die App, Validierung (RFC-1123-artiges
  Label), kein Revert-Timer noetig (kappt die Erreichbarkeit nicht wie
  eine IP-Aenderung). Live getestet inkl. Validierung und Revert.

DHCP: Range ist jetzt Pflicht, kein geratener Default mehr:
- dhcp_range_start/end/dns starten leer (Platzhaltertext statt fake-
  echt aussehendem Default) - ein Zufalls-Range haette den Dienst sonst
  unbemerkt mit einer zum echten Netz nicht passenden Konfiguration
  starten lassen koennen.
- write_dhcp_file und dhcp_service_action=enable_restart verweigern sich
  ohne eingetragene Range; _render_kea_config laesst "pools" ohne Range
  komplett weg statt einen kaputten Pool-String zu erzeugen.
- Neues, separates dhcp_gateway-Feld (optional) fuer einen vom
  automatisch erkannten Gateway abweichenden Router fuer die Clients.

Topbar-Ampel fuer den Kea-Dienst:
- Gruener/roter Punkt neben dem Pruefintervall-Timer, nur sichtbar mit
  settings_dhcp.view (ein einzelner, kurzer systemctl-Aufruf pro Request,
  nicht die volle Status-Erkennung). Live verifiziert (rot wenn gestoppt,
  gruen wenn gestartet).

Reservierungstabelle zeigt jetzt pro Client, welche eigene DHCP-Option
greift (global oder Client-Override, mit Wert im Tooltip).

Text-Straffung: die laengsten Hint-Texte und Code-Kommentare in
Templates/app.py gekuerzt (u.a. groups.html, settings_dhcp.html,
account.html, devices/switches/users/credentials.html, zwei grosse
Migrations-/DHCP-Kommentarbloecke in app.py) - Kernaussagen erhalten,
Redundanz entfernt.

Quelltext-Huerde (KEINE echte Sicherheit, nur Abschreckung): Rechtsklick
und DevTools-/Quelltext-Shortcuts per JS blockiert. Klar dokumentiert
in Kommentar + README, dass der Browser HTML/CSS/JS immer vollstaendig
ausliefert und das in Sekunden umgehbar ist - echte Absicherung bleiben
ausschliesslich die serverseitigen Rechteprüfungen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:28:29 +02:00
alientimandClaude Sonnet 5 82edf7628b Neu: Netzwerkeinstellungen des Hosts unter Systemeinstellungen (IP/DNS/DHCP)
Bisher enthielt "Systemeinstellungen" nur das Pruefintervall. Jetzt
zusaetzlich Verwaltung der Netzwerkkonfiguration DIESES Hosts (getrennt
vom Kea-DHCP-*Server* fuer Clients):

- Backend-Erkennung (NetworkManager vs. dhcpcd via systemctl is-active).
  Kein erkanntes Backend -> Seite bleibt rein lesend statt zu raten.
- Status-Anzeige: Interface, IP/Prefix, Gateway, DNS, Modus (DHCP/Statisch).
- Aenderung: Interface, Modus, bei Statisch IP/Prefix/Gateway, DNS
  unabhaengig von Modus setzbar (IP per DHCP + feste DNS moeglich).
- Sicherheitsnetz analog "netplan try": Backup vor jeder Aenderung,
  automatisches Rollback nach 45s ohne explizite Bestaetigung (nur
  moeglich, wenn die Seite ueber die neue Config noch erreichbar ist) --
  verhindert Aussperren durch einen Tippfehler bei IP/Gateway.
- Bugfix waehrend der Implementierung gefunden: der Auto-Revert-Timer
  laeuft in einem Hintergrund-Thread ohne Request-Kontext: log_action()
  griff auf current_user zu und warf dort einen AttributeError (die
  Config wurde trotzdem korrekt zurueckgerollt, nur der Audit-Log-Eintrag
  fehlte und ein Fehler landete im Server-Log). Fix: log_action_system()
  ohne current_user-Abhaengigkeit fuer Code ausserhalb des Request-
  Kontexts.
- Getestet: Backend-Erkennung + rein lesender Fallback live (WSL hat
  weder NetworkManager noch dhcpcd aktiv, korrekt erkannt), Rechte-Gating,
  Anwenden-/Rollback-Logik fuer beide Backends per gemocktem
  subprocess.run verifiziert (inkl. echtem dhcpcd.conf-Rewrite und
  vollständigem Anwenden-dann-Auto-Rollback-Durchlauf mit Audit-Log-
  Eintrag).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 10:50:33 +02:00
alientimandClaude Sonnet 5 197af26d72 Navbar-Reihenfolge: Unterpunkte je Gruppe editierbar, Karte unter Profil/Passwort verschoben
- Neuer Settings-Schluessel nav_child_order (JSON dict group_key ->
  geordnete Liste der Kind-Keys), analog zu nav_order fuer die Top-Level-
  Reihenfolge. _ordered_nav_items() liefert die komplette, ungefilterte
  Navbar-Struktur inkl. angewandter Kind-Reihenfolge; inject_nav() filtert
  das anschliessend weiterhin nach Berechtigung wie bisher.
- save_nav_order() verarbeitet zusaetzlich ein Feld
  nav_child_order_<group_key> pro Gruppe mit Kindern.
- account.html: Navbar-Reihenfolge-Karte jetzt UNTER Profil/Passwort
  aendern (vorher darueber), zeigt die komplette Navbar inkl.
  eingerueckter, separat sortierbarer Unterpunkte pro Gruppe
  (verschachtelte <ul>, moveNavItem() bewegt automatisch nur innerhalb
  der eigenen Gruppe dank DOM-Nesting).
- Live per Playwright verifiziert: Zugangsdaten innerhalb Geraete nach
  oben verschoben, gespeichert, Sidebar zeigt danach tatsaechlich die
  neue Reihenfolge (Zugangsdaten vor Clients/Switche).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 10:34:31 +02:00
alientimandClaude Sonnet 5 9a390e740d Gruppen-Dropdown: kompaktere Rechtetabelle, Modal bleibt unveraendert
- permission_tree() bekommt einen dritten Parameter compact=false, der
  nur im Dropdown der Hauptseite (Admin-/System-/editierbare Gruppen)
  auf true gesetzt wird, nicht im "Neue Gruppe"-Modal.
- .permission-groups-row--compact verkleinert Zeilen-/Spalten-Padding
  und die Einrueckung der Unterpunkte (32px statt 48px) NUR fuer diese
  Instanzen - das Modal behaelt exakt sein bisheriges Aussehen.
- Modal und Dropdown nutzen weiterhin denselben Code/dieselbe Struktur
  (permission_table()-Macro, Kill-Switch-Logik, JS-Gating), nur die
  Abstaende unterscheiden sich ueber die Zusatzklasse.
- Verifiziert per Screenshot: Dropdown jetzt sichtbar kompakter (engere
  Zellen, kuerzere Zeilen), Modal unveraendert; Gruppen anlegen-mit-
  Rechten und Gruppen-Bearbeiten-Regression funktionieren weiterhin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 10:25:13 +02:00
alientimandClaude Sonnet 5 3d17728e75 Alle Anlegen-Modals einheitlich auf max-width:1000px
- addGroupModal, addCredentialModal, addOptionModal (DHCP), addSwitchModal,
  deviceModal (Gerät anlegen), userModal (Benutzer anlegen) — bisher
  unterschiedliche/keine explizite Breite, jetzt einheitlich 1000px für
  ein konsistentes Erscheinungsbild. Bearbeiten-/Zuweisen-Modals bleiben
  bei ihrer bisherigen (kleineren) Breite, da dort keine so breite
  Rechtetabelle wie bei Neue Gruppe eingebettet ist.
- Live verifiziert: Modal-Breite tatsächlich 1000px (vorher lieferte ein
  vergessener Flask-Neustart fälschlich 900px trotz bereits geänderter
  Vorlage — Erinnerung: Template-Änderungen brauchen ohne
  TEMPLATES_AUTO_RELOAD einen Neustart, anders als CSS/JS).
- Gruppen-Rechtetabelle bei 1440px (typische Breite dieser App) identisch
  zum Neue-Gruppe-Modal nebeneinander, ohne Umbruch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 10:05:58 +02:00
alientimandClaude Sonnet 5 bb3a6ab3a5 Gruppen-Rechtetabelle: Einrückung der Unterpunkte auf 48px erhöht
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 09:53:27 +02:00
alientimandClaude Sonnet 5 7d7239e1af Fix: Benutzer-Löschen im Browser kaputt (JS), Gruppen-Rechtetabelle überarbeitet
Bugfix (von dir gemeldet, live auf Port 5000 gefunden):
- initConfirmables() in app.js rief beim bestätigten Absenden
  form.requestSubmit() OHNE den ursprünglichen Submitter auf. Laut Spec
  wird dann KEIN Submit-Button-Name/Value mitgeschickt. users.html trug
  die Benutzer-ID als name/value des Löschen-Buttons selbst (statt eines
  verstecken Inputs wie überall sonst) — dadurch kam "delete_user" nie
  im Request an, der Klick tat scheinbar nichts. Fix: e.submitter beim
  ersten Klick merken und an requestSubmit(submitter) weiterreichen
  (behebt es grundsätzlich für alle Formulare dieser Art); zusätzlich
  users.html auf das robustere hidden-input-Muster umgestellt wie
  credentials/devices/groups. Mit echtem Playwright-Browserklick
  (nicht nur curl) verifiziert.

Gruppen-Rechtetabelle, mehrere Korrekturrunden nach deinem Mockup:
- Bereichsname + Kill-Switch-Kästchen jetzt eng nebeneinander in der
  Tabellen-Kopfzeile (nicht mehr an den Rand gestellt), R/W/E/D-Spalten
  in derselben Zeile statt einer zweiten.
- Nur die von einem Bereich tatsächlich genutzten Spalten werden gezeigt
  (GROUP_ROW_TYPES) — Logs zeigt nur R, Einstellungen nur R/W/E, nur
  Geräte zeigt zusätzlich D.
- Bugfix: D-Spalte bei Geräte verschwand durch zu schmale Flex-Breite der
  Spalte im Overflow, obwohl serverseitig korrekt gerendert — behoben
  durch flex:0 0 auto statt fester Breite.
- Checkbox-Zentrierung robust per display:block+margin:auto statt sich
  auf native Checkbox-Ränder zu verlassen (waren leicht asymmetrisch).
- Unterpunkte deutlich weiter eingerückt (34px) für klare Hierarchie.
- Sichtbares Ausgrauen (nicht nur natives disabled) der Unterpunkte,
  solange der Bereich-Kill-Switch aus ist (JS setzt .permission-locked).
- Legende jetzt UNTER der Rechtetabelle statt darüber.
- Rechte lassen sich jetzt direkt beim Anlegen einer neuen Gruppe setzen
  (Modal zeigt dieselbe Rechtetabelle, add_group-Route verarbeitet
  mitgesendete permissions[] im selben Request) statt sie erst danach
  über die Gruppentabelle nachzupflegen.

Alles inkl. Erstellen-mit-Rechten und Bearbeiten-Regression live per
Playwright verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 09:52:01 +02:00
alientimandClaude Sonnet 5 8e4a874943 DHCP: Subnet/Netzmaske/Gateway immer aus System-Netzwerkkonfiguration ableiten
- Neue Helfer _list_network_interfaces() (echte Interfaces aus
  /sys/class/net) und _detect_interface_network() (IPv4/Prefix per
  "ip addr show", Gateway per "ip route show default") — Subnet/
  Netzmaske/Router werden dadurch bei jeder Anzeige/Generierung live vom
  System gelesen statt manuell gepflegt zu werden.
- dhcp_subnet/dhcp_netmask als manuelle Settings entfernt; Interface ist
  jetzt ein Dropdown mit den tatsächlich vorhandenen Interfaces statt
  Freitext, serverseitig zusätzlich gegen die echte Liste validiert.
- _dhcp_reservation_candidates()/_render_kea_config() nehmen jetzt das
  erkannte Netz (ipaddress.IPv4Network) bzw. net_info entgegen statt
  Subnet/Netzmaske aus der Konfiguration zu lesen; Router kommt vom
  erkannten Gateway (Fallback: erster DNS-Eintrag, wie zuvor).
- UI zeigt das erkannte Netz (IP/Prefix, Subnet, Gateway) read-only an;
  Schreiben/Vorschau werden blockiert bzw. liefern eine leere
  Reservierungsliste, wenn die Erkennung fehlschlägt, statt eine mit
  Sicherheit falsche Konfiguration zu erzeugen.
- Live getestet: Erkennung liefert korrekt das tatsächliche WSL-NAT-Netz
  (172.25.64.0/20) samt Gateway; generierte Config besteht kea-dhcp4 -t;
  ein Testgerät mit IP im erkannten Netz erscheint korrekt als
  Reservierung, Geräte außerhalb werden weiterhin sauber übersprungen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 08:28:11 +02:00
alientimandClaude Sonnet 5 c3d69a9870 DHCP: Backend auf Kea umgestellt, eigene Options (global + pro Client), echte Install-/Dienststeuerung
- Backend-Wechsel von isc-dhcp-server (seit 2022 EOL) auf Kea DHCP
  (kea-dhcp4-server) — aktiv weiterentwickelt, bildet "globaler Wert, pro
  Client überschreibbar" nativ über Kea-Reservation-Option-Data ab.
- Neue Tabellen dhcp_option_defs/dhcp_option_values: eigene/herstellerspezifische
  Options mit Code/Name/Typ/Beschreibung, Wert global sowie optional pro
  Client (device_mac="" für global, siehe Kommentar zur SQLite-NULL-
  UNIQUE-Falle). UI dafür: Optionen-Tabelle + "Neue Option"-Modal +
  Pro-Client-Overrides-Modal je Reservierung.
- Kea-JSON-Generator (_render_kea_config): option-def für jede eigene
  Option, globale Werte im Top-Level option-data, Client-Overrides im
  option-data der jeweiligen Reservierung.
- Reservierungen werden jetzt zusätzlich auf Zugehörigkeit zum
  konfigurierten Subnet gefiltert (_dhcp_reservation_candidates) — beim
  Live-Test gegen echtes Kea gefunden: Kea lehnt Reservierungen außerhalb
  ihres Subnets als Konfigurationsfehler ab, das muss also schon bei der
  Generierung berücksichtigt werden statt erst beim Laden zu crashen.
- Neue, einzeln bestätigte Aktionen: "kea-dhcp4-server installieren"
  (apt-get), "Aktivieren & (neu) starten" sowie "Stoppen"
  (systemctl) — bewusst getrennt von "Konfiguration speichern"/"In
  Datei schreiben".
- Live gegen eine echte, frisch installierte Kea-3.0.3-Instanz verifiziert:
  Installation erfolgreich, generierte Config besteht "kea-dhcp4 -t",
  Dienst übernimmt sie beim Neustart fehlerfrei (DHCP4_CONFIG_COMPLETE),
  globaler Options-Wert und Client-Override erscheinen korrekt getrennt.
  Danach wieder gestoppt; Paket bleibt installiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 21:47:54 +02:00
alientimandClaude Sonnet 5 9eb9cfdda6 Neu: DHCP-Reservierungen aus Client-Stammdaten (Systemeinstellungen -> DHCP)
- Neue Unterseite unter Einstellungen (eigenes Rechtepaar
  settings_dhcp.view/settings_dhcp.edit, in PERMISSIONS/NAV_ITEMS/
  _nav_key_visible integriert, Kill-Switch über settings_group.view
  greift wie bei den anderen Einstellungen-Unterpunkten).
- Rein lesende Installations-/Status-Erkennung (shutil.which("dhcpd"),
  systemctl is-active isc-dhcp-server) — die App installiert/startet nie
  selbst einen DHCP-Dienst, sondern zeigt bei fehlender Installation den
  passenden manuellen Befehl an.
- Konfigurierbare Netzwerkparameter (Interface/Subnet/Netzmaske/Range/
  DNS/Domain/Lease-Zeiten/Ausgabepfad), gespeichert als dhcp_*-Schlüssel
  in der bestehenden settings-Tabelle.
- Reservierungen werden aus aktiven Geräten mit gültiger MAC+IP generiert
  (Hostname aus Gerätename abgeleitet, Kollisionen automatisch
  durchnummeriert, Geräte ohne MAC/IP werden übersprungen).
- "In Datei schreiben" (nur mit settings_dhcp.edit) schreibt eine
  separate Include-Datei statt der aktiven dhcpd.conf; kein automatischer
  Dienst-Reload/-Restart durch die App.
- Live getestet: Status-Erkennung, Config speichern, Reservierungs-
  Generierung inkl. Namenskollisionen, Datei-Schreiben in sicheren
  Testpfad, View-only-Gating (Formular ausgeblendet, POST blockiert),
  Kill-Switch über settings_group.view.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 21:10:05 +02:00
alientimandClaude Sonnet 5 06db4c1382 Switche: optionaler SSH-Port statt fest 22
- Neue Spalte switches.ssh_port (nullable INTEGER), Migration für
  bestehende DBs in _ensure_schema(), Feld in create_db.py ergänzt.
- Formular (Anlegen/Bearbeiten): optionales SSH-Port-Feld, Validierung
  1-65535, leer -> NULL (= Standard 22 überall, SWITCH_DEFAULT_SSH_PORT).
  Switch-Liste zeigt den effektiven Port inkl. "(Standard)"-Hinweis.
- generate_ips.py liefert den effektiven Port (Fallback 22) als eigenes
  Pipe-Feld an poe.sh; poe.sh übernimmt es in disable_poe/enable_poe und
  reicht es als "ssh -p <port>" an die expect-Skripte durch (Default
  weiterhin 22 falls Parameter fehlt).
- Web-Terminal (Verbindungstest) sendet den im Formular eingetragenen
  Port statt hartkodiert 22.
- Live getestet: gültiger/leerer/ungültiger Port beim Anlegen, korrekte
  Weitergabe durch generate_ips.py inkl. Feldreihenfolge, die poe.sh
'read' erwartet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 19:56:23 +02:00
alientimandClaude Sonnet 5 813b17d002 Rechtesystem: Löschen überall in Ändern falten, Gruppen-Tabelle transponieren
- "Ändern" deckt jetzt ausnahmslos überall Löschen mit ab (auch bei
  Clients) — es gibt kein eigenes *.delete-Recht mehr. Betroffen:
  devices/switches/credentials/users/groups.
- "D" in der Rechtetabelle bedeutet ausschließlich Dashboard-Neustart
  (devices.restart, PoE-Neustart über das Dashboard-Popup) und existiert
  nur bei Clients. Automatische Neustarts bei Ausfall (poe.sh) bleiben
  davon komplett unabhängig.
- Gruppen-Rechtetabelle transponiert: Kategorien jetzt als Zeilen,
  Rechte R(ead)/W(rite)/E(dit)/D(ashboard-Neustart) nebeneinander als
  Spalten statt untereinander. Deckt sich besser mit dem Legenden-Format
  und braucht weniger vertikalen Platz.
- Neue, einmalig laufende Migration (_migrated_permission_delete_fold_v2)
  überführt bestehende *.delete-Grants verlustfrei in *.edit, bevor die
  veralteten Rechte-Zeilen entfernt werden.
- Routen (delete_device/delete_switch/delete_credential/delete_user/
  delete_group) und die zugehörigen can_delete-Template-Variablen prüfen
  jetzt einheitlich *.edit statt *.delete.
- README-Rechtesystem-Abschnitt und Legende an das neue Tabellen-Layout
  angepasst.
- Live getestet: Kill-Switch, alle 5 Delete-Foldings (mit/ohne Recht),
  devices.restart-Gating unabhängig von devices.edit, Eskalationsschutz
  (Admin-Löschschutz, Systemgruppen-Schutz) — alles bestätigt korrekt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 19:49:28 +02:00
alientimandClaude Sonnet 5 4b7403f476 Vollständig granulares, an der Navbar gespiegeltes Rechtesystem
Kompletter Umbau des Permission-Systems weg von groben is_admin-Gates hin zu
einem zweistufigen, an die Sidebar-Struktur gespiegelten Rechtebaum:

  Geräte (devices_group.view)
   ├─ Clients      Lesen/Schreiben/Ändern/Löschen (+ PoE-Neustart)
   ├─ Switche      Lesen/Schreiben/Ändern/Löschen
   └─ Zugangsdaten Lesen/Schreiben/Ändern/Löschen (jetzt eigene Rechte,
                    vorher an switches.* gekoppelt)
  Logs (logs_group.view)
   ├─ Live         Lesen
   └─ Änderungen   Lesen
  Einstellungen (settings_group.view)
   ├─ Benutzer            Lesen/Schreiben/Ändern/Löschen
   ├─ Gruppen             Lesen/Schreiben/Ändern/Löschen
   ├─ Systemeinstellungen Lesen/Ändern
   └─ Im-/Export          Lesen (Export)/Ändern (Import)

- User.has_permission() ist jetzt hierarchisch: das "Bereich anzeigen"-Recht
  einer Top-Level-Gruppe wirkt als Kill-Switch für alle Kind-Rechte
  darunter, auch wenn ein Kind-Recht einzeln noch gesetzt ist. Mit
  Testgruppe verifiziert (devices.view ohne devices_group.view -> /devices
  liefert 302, "Geräte" verschwindet komplett aus der Sidebar; nach
  Zurücksetzen sofort wieder 200).
- devices.toggle entfällt, ist jetzt Teil von devices.edit (Ändern).
- Neue eigenständige credentials.*-Rechte statt Kopplung an switches.*.
- Benutzer- und Gruppenverwaltung sind jetzt ebenfalls granular/delegierbar
  (users.*/groups.*) statt fest is_admin-exklusiv — dafür neue,
  fest einprogrammierte Eskalationsschranken: Admin-Konten anlegen/ändern/
  löschen sowie Admin-Zuweisung bleiben unabhängig von delegierten Rechten
  echten Admins vorbehalten (mit Testgruppe verifiziert: Anlegen als Admin,
  Bearbeiten/Löschen bestehender Admin-Konten und Zuweisen zu "admin"
  wurden alle korrekt blockiert, normale Benutzerverwaltung funktioniert).
- "Admin" (virtuell) und "Benutzer" (Standardgruppe, neues is_system-Flag)
  sind jetzt echte Systemgruppen: weder umbenennbar noch in ihren Rechten
  änderbar, auch nicht durch Admins über die UI — Mitgliedschaft bleibt frei
  verwaltbar. Mit direktem POST verifiziert: Umbenennen/Löschen/Rechte-Reset
  von "Benutzer" werden blockiert, Mitgliederverwaltung funktioniert weiter.
- groups.html zeigt den Baum jetzt als 3 Zeilen (Geräte/Logs/Einstellungen)
  mit eingerückten Unterpunkten statt einer flachen Liste von Kategorien mit
  wiederholtem Bereichsnamen im Label.
- Migration in _ensure_schema() (Altrechte übertragen, neue Bereichs-Rechte
  für bestehende Gruppen nachtragen) läuft jetzt über einen Einmal-Guard in
  der settings-Tabelle — lief anfangs bei jedem Neustart erneut und hat
  damit den Kill-Switch-Mechanismus untergraben (ein deaktiviertes
  Bereichs-Recht wäre bei jedem Neustart automatisch wieder gesetzt worden,
  solange irgendein Kind-Recht noch aktiv war); im Test entdeckt und behoben.
- create_db.py synchronisiert (is_system-Spalte, neuer Rechtesatz für
  Frischinstallationen).
- README: Rechtesystem-Abschnitt komplett neu beschrieben (Baum, Kill-Switch,
  Systemgruppen, Eskalationsschutz).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 18:28:42 +02:00
alientimandClaude Sonnet 5 52b14b8ef7 Rechtesystem-Lücke schließen: Live-Log war nur @login_required statt admin-geschützt
Vollständiges Audit aller Routen gegen das Rechtesystem durchgeführt. Die
Nav-Sichtbarkeit stufte "Live" (Live-Log) bereits korrekt als admin-only ein
(_nav_key_visible: logs_live -> user.is_admin), die zugehörigen Routen
/logs und /get_log prüften das aber nie tatsächlich — nur @login_required.
Jeder eingeloggte Benutzer, unabhängig von seinen Gruppen-Rechten, konnte
das Live-Log damit direkt per URL aufrufen, obwohl die Sidebar es korrekt
ausblendete.

- /logs: fehlende is_admin-Prüfung ergänzt (Flash + Redirect, konsistent zu
  activity_log()/users()/groups()/settings()).
- /get_log: fehlende is_admin-Prüfung ergänzt (403, da per AJAX abgerufen).

Alle anderen neu hinzugekommenen Seiten (Benutzer, Gruppen, Systemein-
stellungen, Im-/Export, Änderungslog, Jetzt-prüfen-Trigger) waren bereits
korrekt und bewusst exklusiv auf is_admin geprüft — dort gibt es keine
sinnvolle granularere Rechte-Stufe unterhalb von Admin (Gruppenverwaltung
steuert das Rechtesystem selbst, Systemeinstellungen/Import-Export/manuelle
Prüfung wirken global). Zugangsdaten bleiben bewusst an switches.*-Rechte
gekoppelt (bestehende, dokumentierte Design-Entscheidung). README um eine
Übersicht ergänzt, welche Bereiche bewusst ohne granulare Rechte auskommen
und warum.

Mit Testbenutzer ohne jede Gruppenzugehörigkeit verifiziert: /logs liefert
jetzt 302 (Redirect), /get_log liefert 403 statt beide zuvor 200.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 16:01:39 +02:00
alientimandClaude Sonnet 5 c4f682b5de UI-Fixes (Button-Wrap, Suchfeld-Icon, Log-Farben, Nav-Persistenz), manueller Check-Trigger, echter Live-Refresh
- section-head: Button ("+ Neue Zugangsdaten" etc.) rutscht bei langer
  Beschreibung nicht mehr in die nächste Zeile — bleibt immer rechts oben,
  der Beschreibungstext wickelt stattdessen innerhalb seiner eigenen Spalte.
- Suchfeld-Icon überlappte den Platzhaltertext: eine spätere, generische
  Input-Regel mit gleicher Spezifität hat die padding-left-Regel des
  Lupe-Icons überschrieben (reine Reihenfolge-im-Stylesheet-Sache) — Selektor
  spezifischer gemacht.
- Live-Log: Neustart-bezogene Zeilen (manueller Neustart, automatischer
  PoE-Restart bei Ausfall) werden jetzt orange markiert statt farblos.
- Aufgeklappte Nav-Gruppen bleiben über Seitenwechsel hinweg erhalten
  (localStorage), zusätzlich zur automatischen Aufklappung der Gruppe der
  aktuell aktiven Seite.
- Datei-Auswahl (Import) im Dark Mode gestylt (Text + Button), inkl.
  color-scheme-bewusster Browser-Widgets.
- Manueller "Jetzt prüfen"-Trigger (Icon neben dem globalen Timer, nur
  Admins): startet rpi-check.service neu -> frisches Logfile + sofortiger
  Durchlauf, Countdown synchronisiert sich auf den manuellen Zeitpunkt.
- Dashboard und Live-Log aktualisieren sich jetzt per AJAX im Hintergrund
  (kein voller Seiten-Reload mehr): neue Route /dashboard/tiles liefert das
  Kachel-Fragment (_dashboard_tiles.html, von index.html eingebunden und vom
  Live-Update nachgeladen), get_log() liefert den aktuellen Dateinamen als
  Header mit, damit die Anzeige nach einem Service-Neustart aktuell bleibt.
- Root-Cause für "Intervall bleibt immer bei 300s": in der WSL-Testumgebung
  lief poe_wrapper.py nur als manuell gestarteter Hintergrundprozess statt
  als echter rpi-check.service — "systemctl restart" lief dadurch ins Leere.
  Echten systemd-Service in WSL eingerichtet und Intervalländerung end-to-end
  verifiziert (Log-Zeilenabstand folgt jetzt dem neu gesetzten Intervall).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:53:15 +02:00
alientimandClaude Sonnet 5 0fcb6c94cb Topbar auf globalen Prüf-Timer reduziert, Aktionen+Beschreibung über Tabellen, sortierbare Listen
- Topbar zeigt rechts jetzt ausschließlich den "Nächste Prüfung"-Countdown,
  konsistent auf jeder Seite (auch anonymes Dashboard) statt nur auf dem
  Dashboard. Neuer Context-Processor inject_check_timer()/get_last_run_at()
  liefert last_run/interval global, ohne dass jede Route das selbst
  berechnen muss.
- "+ Neu ..."-Buttons (Devices, Switches, Zugangsdaten, Benutzer, Gruppen)
  aus der Topbar entfernt und stattdessen in einen .section-head direkt
  über der jeweiligen Tabelle verschoben, zusammen mit einer kurzen
  Beschreibung der Seite (bisher nur bei Zugangsdaten/Gruppen vorhanden,
  jetzt auch bei Geräte/Switche/Benutzer).
- Live-Log: eigene lokale Timer-Pill entfernt (redundant zum globalen
  Timer), "Aktualisieren"-Button in denselben section-head verschoben.
  Dashboard: Suchfeld aus der Topbar in den Seiteninhalt verschoben, lokale
  Timer-Anzeige entfernt (übernimmt die globale Topbar-Pill), Reload-bei-
  Intervallende-Logik bleibt als separater, unsichtbarer Scheduler erhalten.
- Neue generische Tabellen-Sortierung (app.js: initSortableTables): Klick
  auf eine Spaltenüberschrift mit data-sort-key sortiert die Zeilen anhand
  von data-sort-<key>-Attributen. Unterstützt auch Akkordeon-Tabellen mit
  mehreren <tbody> (Gruppen: Haupt- + Detail-Zeile bleiben als Einheit
  zusammen, die virtuelle "Admin"-Zeile bleibt über data-sort-pinned immer
  oben). Angewendet auf Geräte, Switche, Zugangsdaten, Benutzer, Gruppen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:21:30 +02:00
alientimandClaude Sonnet 5 e4cceb4091 poe.sh: CRLF-Zeilenenden, Feldanzahl und Switch-Gating fixen
- Datei hatte CRLF-Zeilenenden (vermutlich aus einer Windows-Bearbeitung),
  wodurch das Skript unter Linux/WSL mit "command not found: $'\r'" und
  Syntaxfehlern abbrach. Auf LF normalisiert.
- Die Haupt-Check-Schleife las per `read` nur 7 Felder aus
  generate_ips.py, das aber inzwischen 8 Felder liefert (MAC am Ende
  angehängt) — dadurch landete die MAC-Adresse an $switch_pass
  angehängt. `mac` als zusätzliche (ungenutzte) read-Variable ergänzt.
- PoE-Neustart wurde bei nicht erreichbaren Geräten ausgelöst, sobald ein
  `port`-Wert gesetzt war — unabhängig davon, ob dem Gerät überhaupt ein
  Switch zugeordnet ist. Das führte zu sinnlosen SSH-Versuchen mit leeren
  Zugangsdaten (ssh @) und irreführenden "PoE deaktiviert/aktiviert"
  Log-Einträgen für Geräte ohne Switch. Prüft jetzt zusätzlich, dass
  switch_ip nicht leer ist, bevor ein Neustart versucht wird (gilt für
  den regulären Check-Loop und den manuellen Neustart).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 14:56:53 +02:00
alientimandClaude Sonnet 5 37dac64abc Live-Log-Fix, Sidebar-Untermenüs für Geräte/Einstellungen, eigenes Konto
- Live-Log-Bug behoben: _latest_log_file() sortierte nach Datei-ctime statt
  nach Dateiname — auf manchen Dateisystemen (u.a. unter WSL2 auf einem
  gemounteten Windows-Laufwerk) unzuverlässig und lieferte nicht immer das
  tatsächlich neueste Logfile. Sortiert jetzt wie der Rest des Codes über
  den chronologisch sortierbaren Dateinamen (rpi-YYYYMMDDHHMMSS.log).
- Navbar umstrukturiert in aufklappbare Gruppen mit Unterpunkten:
  - "Geräte": Clients (bisherige Devices-Seite), Switche, Zugangsdaten
  - "Einstellungen": Benutzer, Gruppen, Systemeinstellungen (Prüfintervall),
    Im-/Export
  - "Logs": Live, Änderungen (vormals Live-Log/Änderungslog)
  Sichtbarkeit gilt jetzt auch pro Unterpunkt: eine Gruppe erscheint nur,
  wenn mindestens ein Unterpunkt für den Benutzer sichtbar ist, und zeigt
  dann auch nur die sichtbaren Unterpunkte.
- Im-/Export als eigene Unterseite mit Export/Import nebeneinander
  (.settings-grid).
- Neue Seite "Mein Konto" (/account, erreichbar über ein Zahnrad-Symbol
  neben dem eigenen Namen in der Sidebar) ersetzt das bisherige Profil-Modal:
  Profil/Passwort/Profilbild als volle Seite, dazu für Admins die
  Navbar-Reihenfolge (aus den Systemeinstellungen hierher verschoben).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 14:44:02 +02:00