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>
748 lines
44 KiB
Markdown
748 lines
44 KiB
Markdown
# PoE Manager — Modern UI Rebuild
|
|
|
|
Webbasierte Verwaltung und Monitoring von PoE-Devices und Aruba-Switchen —
|
|
funktionales Rebuild von [Aruba-PoE](https://gitea.int.eertmoed.net/WiS/Aruba-PoE)
|
|
mit komplett neuem, modernem Frontend und einem granularen Rechtesystem.
|
|
|
|
**Das Projekt ist ausschließlich für Linux ausgelegt** (Zielsystem: eine
|
|
Linux-VM). Entwickelt wird unter WSL2 (Ubuntu) unter Windows — siehe
|
|
[Entwicklung unter WSL](#entwicklung-unter-wsl) weiter unten.
|
|
|
|
Die App ermöglicht:
|
|
|
|
- **Öffentliches Kachel-Dashboard ohne Login** — zeigt nur aktive (nicht
|
|
deaktivierte) Geräte, nur Online/Offline/Gesamt-Statistik; volle Verwaltung,
|
|
Aktivieren/Deaktivieren und PoE-Neustart bleiben hinter dem Login. Eingeloggt
|
|
sind zusätzlich deaktivierte Geräte sichtbar (eigene Kachel-Sektion). Alle
|
|
Kacheln sind durchsuchbar und in drei Abschnitten sortiert: Offline, Online,
|
|
Deaktiviert — jeweils alphabetisch
|
|
- Verwaltung von Devices und Switches
|
|
- **Zugangsdaten als eigene, wiederverwendbare Entität**: SSH-Logins werden
|
|
einmal angelegt und können mehreren Switchen zugeordnet werden; beim
|
|
Anlegen eines Switches lassen sich bestehende Zugangsdaten auswählen oder
|
|
direkt neue hinterlegen, ohne vorher zu „Zugangsdaten“ wechseln zu müssen
|
|
- **Browser-SSH-Terminal beim Anlegen/Bearbeiten eines Switches**, um den
|
|
Host-Key zu bestätigen und die Zugangsdaten direkt zu prüfen, bevor der
|
|
Switch gespeichert wird — funktioniert auf jedem Betriebssystem, die
|
|
SSH-Verbindung wird dabei immer vom PoE-Server aus aufgebaut
|
|
- Manueller PoE-Neustart einzelner Geräte per Klick — ausschließlich über
|
|
den zugeordneten Switch (`poe.sh`); der Button erscheint im Detail-Popup
|
|
nur, wenn dem Gerät tatsächlich Switch **und** Port zugeordnet sind (sonst
|
|
wäre kein PoE-Reset möglich), und ist immer der am weitesten links stehende
|
|
Aktions-Button — „Schließen“ steht unabhängig davon immer ganz rechts. Bei
|
|
deaktivierten Geräten bietet das Detail-Popup stattdessen direkt eine
|
|
Aktivieren-Option
|
|
- **SSH-basierte Wartung für Linux-Clients** (eigene Seite „Wartung“,
|
|
getrennt vom PoE-Neustart oben): Bulk-Update (`apt update && apt upgrade`)
|
|
und Neustart per SSH direkt auf dem Gerät, unabhängig von jeder
|
|
Switch-Zuordnung (siehe [Wartung](#wartung-ssh-bulk-update--neustart))
|
|
- Einstellbares Prüfintervall (steuert den Hintergrund-Check-Dienst) —
|
|
der Countdown „Nächste Prüfung“ wird aus dem tatsächlichen letzten
|
|
Log-Eintrag berechnet, nicht bei jedem Seitenaufruf neu gestartet, und
|
|
läuft **global in der Topbar auf jeder Seite** (nicht nur im Dashboard)
|
|
- Live-Log-Ansicht mit farblicher Hervorhebung (online/offline/Neustart);
|
|
Dashboard und Live-Log aktualisieren sich automatisch im Hintergrund
|
|
(per AJAX, ohne vollen Seiten-Reload). Ein „Jetzt prüfen"-Icon neben dem
|
|
Timer (nur Admins) startet manuell einen sofortigen Prüf-Durchlauf —
|
|
der Countdown setzt sich dabei auf den manuell ausgelösten Zeitpunkt zurück
|
|
- **Einheitliches Seiten-Layout**: die Topbar zeigt rechts ausschließlich
|
|
den „Nächste Prüfung“-Timer; Anlegen-Buttons und eine kurze Beschreibung
|
|
sitzen stattdessen direkt über der jeweiligen Tabelle. Die Listen
|
|
(Geräte, Switche, Zugangsdaten, Benutzer, Gruppen) sind per Klick auf die
|
|
Spaltenüberschrift sortierbar
|
|
- **Rechtesystem mit Gruppen**: Admins dürfen immer alles (als Gruppe „Admin“
|
|
überall mit aufgeführt); normalen Benutzern lassen sich über Gruppen
|
|
einzelne Verwaltungsrechte gezielt zuweisen, inkl. reiner Ansichtsrechte
|
|
(siehe [Rechtesystem](#rechtesystem-gruppen--berechtigungen))
|
|
- Einklappbare Sidebar (Desktop: dauerhaft, über Neuladen hinweg gemerkt;
|
|
mobil: temporäres Überlagern)
|
|
- **Änderungslog**: jede Anlage/Bearbeitung/Löschung sowie Aktivieren/
|
|
Deaktivieren von Geräten, Switchen, Zugangsdaten, Benutzern und Gruppen
|
|
wird protokolliert (wer, wann, was) — einsehbar unter „Logs → Änderungslog“;
|
|
Geräte und Switche merken sich zusätzlich direkt am Datensatz, wer sie
|
|
zuletzt geändert hat. PoE-Neustarts werden bewusst **nicht** geloggt
|
|
(siehe [Änderungslog](#änderungslog))
|
|
- **Verschlüsselter Import/Export**: Geräte, Switche und Zugangsdaten lassen
|
|
sich unter „Einstellungen → Im-/Export" als ein mit selbstgewählter
|
|
Passphrase verschlüsseltes Bundle exportieren und auf einer neuen Umgebung
|
|
wieder importieren (siehe [Import/Export](#importexport))
|
|
- **Eigenes Konto**: das Zahnrad-Symbol neben dem eigenen Namen unten in der
|
|
Sidebar führt zu „Mein Konto“ — Vor-/Nachname, eigenes Passwort und
|
|
Profilbild pflegen (siehe [Eigenes Konto](#eigenes-konto--profilbild))
|
|
- **Anpassbare Navigation**: Admins legen dort zusätzlich die Reihenfolge der
|
|
Sidebar-Hauptpunkte per Auf-/Ab-Buttons fest; jeder Benutzer sieht davon
|
|
ohnehin nur, wofür er berechtigt ist — auch pro Unterpunkt innerhalb einer
|
|
Gruppe (siehe [Navigation anpassen](#navigation-anpassen))
|
|
|
|
## Was ist neu gegenüber dem Original?
|
|
|
|
- **Komplett neues Frontend**: eigenes Design-System (kein Bootstrap mehr),
|
|
Logo mittig in der Topbar (auf jeder Seite, skaliert auf die Balkenhöhe),
|
|
Sidebar-Navigation (nur für eingeloggte Nutzer sichtbar), Dark/Light-
|
|
Umschalter, Karten-Dashboard mit Live-Zählern, moderne Modals/Toasts statt
|
|
Browser-`confirm()` und Bootstrap-Alerts.
|
|
- **Dashboard jetzt teilweise öffentlich**: `/` ist ohne Login erreichbar und
|
|
zeigt eine reduzierte Lese-Ansicht (Hostname, IP, Status, letzte Prüfung)
|
|
mit Login-Button; alle Verwaltungsseiten und der PoE-Neustart bleiben
|
|
`@login_required` und zusätzlich rechtebasiert (s.u.).
|
|
- **Browser-SSH-Terminal für Switche**, implementiert mit **paramiko** (reines
|
|
Python) statt eines lokalen pty + System-`ssh`-Client — läuft dadurch
|
|
plattformunabhängig. Die SSH-Verbindung zum Switch wird immer serverseitig
|
|
(vom PoE-Server) aufgebaut, nie vom Browser/Client aus.
|
|
- **Granulares Rechtesystem mit Gruppen** (neu, siehe unten) statt der
|
|
ursprünglichen reinen Admin/User-Unterscheidung.
|
|
- **Gleiche Kernlogik**: Datenbankschema (erweitert um Gruppen/Rechte),
|
|
Login, PoE-Restart-Mechanismus (`poe.sh` per SSH/`expect` auf die
|
|
Aruba-Switche) sind ansonsten unverändert.
|
|
- **Aufgeräumtes Backend**: konsistente, konfigurierbare Pfade (`POE_DB_PATH`,
|
|
`POE_SCRIPT`, `POE_LOG_GLOB`, …) statt hart codierter/inkonsistenter Pfade;
|
|
Secret-Key und Fernet-Key werden beim ersten Start automatisch generiert
|
|
statt im Repo zu liegen.
|
|
|
|
> ⚠️ **Sicherheitshinweis zum Original-Repo:** Im Original sind `sqlite.db`
|
|
> (Nutzer-Passwort-Hashes, verschlüsselte Switch-Passwörter) und `fernet.key`
|
|
> (der zugehörige Schlüssel) mit im Git-Repo eingecheckt — die `.gitignore`
|
|
> dort schließt sie über die `!`-Regeln versehentlich wieder ein. In diesem
|
|
> Rebuild sind beide Dateien in `.gitignore` ausgeschlossen und werden beim
|
|
> ersten Start frisch erzeugt. Empfehlung fürs Original-Repo: beide Dateien
|
|
> aus der Git-Historie entfernen und Fernet-Key sowie alle Switch-/User-
|
|
> Passwörter rotieren.
|
|
|
|
## Rechtesystem (Gruppen & Berechtigungen)
|
|
|
|
Admins dürfen immer alles. Zusätzlich gibt es **Gruppen** mit einem
|
|
**vollständig granularen, an der Navbar gespiegelten Rechtebaum** — jeder
|
|
Bereich der App (nicht nur Devices/Switches) lässt sich einzeln freischalten.
|
|
Ein Benutzer kann mehreren Gruppen angehören, die Rechte addieren sich
|
|
(Vereinigung, nicht Schnittmenge).
|
|
|
|
### Zweistufiger Rechtebaum
|
|
|
|
Der Baum hat genau zwei Ebenen, exakt gespiegelt an den drei Sidebar-Gruppen
|
|
(Geräte/Logs/Einstellungen). Auf der Gruppen-Seite stehen sie als drei
|
|
kompakte Spalten nebeneinander, mit einer kurzen Legende direkt darüber.
|
|
Jede Spalte ist eine kleine Tabelle: die Kopfzeile enthält in EINER Zeile
|
|
sowohl den Bereichsnamen mit seinem Kästchen (dem Kill-Switch, dicht
|
|
nebeneinander statt an den Rand gestellt) als auch die Buchstaben-Spalten
|
|
**R**(ead/Lesen), **W**(rite/Schreiben, Anlegen), **E**(dit/Ändern) — nur
|
|
die Rechte, die dieser Bereich tatsächlich nutzt. Darunter je eine
|
|
eingerückte Zeile pro Unterpunkt. **„Ändern“ deckt Löschen überall mit
|
|
ab** — es gibt bewusst kein eigenes Löschen-Recht. Eine vierte Spalte **D**
|
|
gibt es nur bei Geräte → Clients, und sie bedeutet dort **nicht** Löschen,
|
|
sondern *PoE-Neustart über das Dashboard-Popup auslösen* (automatische
|
|
Neustarts bei Ausfall über `poe.sh` sind davon komplett unabhängig und
|
|
laufen immer). Für Zeilen ohne passendes Recht (z.B. „D“ bei Switche/
|
|
Zugangsdaten/Benutzer/Gruppen) wird eine ausgegraute, nicht anklickbare
|
|
Checkbox angezeigt statt die Zelle wegzulassen:
|
|
|
|
```
|
|
☑ Geräte R W E D ☑ Logs R W E ☑ Einstellungen R W E
|
|
Clients ☑ ☑ ☑ ☑ Live ☑ ☑ ☑ Benutzer ☑ ☑ ☑
|
|
Switche ☑ ☑ ☑ ▢ Änd. ☑ ☑ ☑ Gruppen ☑ ☑ ☑
|
|
Zugangsd. ☑ ☑ ☑ ▢ Systemeinst. ☑ ▢ ☑
|
|
Im-/Export ☑ ▢ ☑
|
|
```
|
|
|
|
Das jeweilige „Bereich sichtbar“-Recht (`devices_group.view` /
|
|
`logs_group.view` / `settings_group.view`, das Kästchen in der Kopfzeile
|
|
einer Spalte) wirkt als **Kill-Switch**: ist es für eine Gruppe nicht
|
|
gesetzt, greift kein einziges Recht darunter mehr — selbst wenn z.B.
|
|
`devices.view` einzeln noch angehakt ist. So lässt sich ein ganzer Bereich
|
|
mit einem Klick sperren, ohne jedes Unterrecht einzeln zurücknehmen zu
|
|
müssen (`User.has_permission()` in `app.py`). Auf der Gruppen-Seite sind
|
|
die Unterpunkt-Zeilen einer Spalte per JavaScript so lange gesperrt UND
|
|
sichtbar ausgegraut (nicht nur das dezente native disabled-Styling), bis
|
|
das Kopfzeilen-Kästchen angehakt ist.
|
|
|
|
### Systemgruppen
|
|
|
|
Zwei Gruppen sind fest und **weder umbenennbar noch in ihren Rechten
|
|
änderbar** (auch nicht durch Admins über die UI) — Mitgliedschaft bleibt bei
|
|
beiden frei verwaltbar:
|
|
- **Admin** — virtuell (kein echter `groups`-Datensatz), intern über den
|
|
`is_admin`-Schalter der Benutzer gesteuert, immer alle Rechte. Mindestens
|
|
ein Admin muss bestehen bleiben (serverseitig erzwungen).
|
|
- **Benutzer** — die Standardgruppe (`is_system`-Flag), der jeder neu
|
|
angelegte Nicht-Admin automatisch zugeordnet wird. Fester Rechtesatz:
|
|
Geräte-Bereich + Clients/Switche lesen, Logs-Bereich + Live-Log lesen.
|
|
|
|
Alle anderen Gruppen sind vom jeweiligen Rechteinhaber (`groups.edit`, das
|
|
auch Anlegen und Löschen von Gruppen umfasst) frei konfigurierbar.
|
|
|
|
### Eskalationsschutz
|
|
|
|
Da jetzt auch **Benutzer-** und **Gruppenverwaltung** delegierbar sind (z.B.
|
|
eine Gruppe mit nur `users.edit`, ohne Admin zu sein), gelten zusätzlich zu
|
|
den granularen Rechten diese fest einprogrammierten Schranken, unabhängig
|
|
davon, was eine Gruppe an Rechten hat:
|
|
- Ein Benutzer als Admin anlegen/dazu machen (`group_id=admin` bei
|
|
Anlegen/Zuweisen) bleibt echten Admins vorbehalten.
|
|
- Ein bestehendes Admin-Konto bearbeiten, löschen oder ihm die Gruppe ändern
|
|
bleibt echten Admins vorbehalten.
|
|
- Wer Admin ist, wird ausschließlich über „Admin-Mitglieder verwalten“ auf
|
|
der Gruppen-Seite gesteuert — bleibt admin-exklusiv, unabhängig von
|
|
`groups.edit`.
|
|
|
|
Sowohl das Anzeigen jeder Seite als auch jede einzelne Aktion (Buttons,
|
|
Formulare) ist an das jeweilige Recht gekoppelt — im Frontend ausgeblendet
|
|
**und** im Backend serverseitig durchgesetzt, unabhängig vom Frontend.
|
|
|
|
Datenmodell: `groups` (inkl. `is_default`- und `is_system`-Flag),
|
|
`group_permissions` (Gruppe → Recht), `user_groups` (Benutzer → Gruppe).
|
|
Bestehende Datenbanken werden beim App-Start automatisch migriert
|
|
(`_ensure_schema()` in `app.py`): `devices.toggle` → `devices.edit`,
|
|
`switches.*` → gespiegelte `credentials.*`, nachträgliches Setzen der neuen
|
|
Bereichs-Rechte für bereits vergebene Unterrechte, sowie (spätere Revision)
|
|
sämtliche `*.delete`-Rechte → `*.edit` (Löschen wurde nachträglich überall in
|
|
Ändern gefaltet). Jede dieser Migrationen läuft **nur einmalig** über einen
|
|
eigenen Guard in der `settings`-Tabelle, damit z.B. ein bewusst deaktiviertes
|
|
Bereichs-Recht nicht bei jedem Neustart automatisch wieder gesetzt wird.
|
|
|
|
## Zugangsdaten (wiederverwendbare SSH-Logins)
|
|
|
|
Zugangsdaten (Username + verschlüsseltes Passwort) sind eine eigene Entität
|
|
(`credentials`-Tabelle) statt direkt am Switch/Gerät zu hängen — mehrere
|
|
Switche/Geräte mit demselben Login teilen sich einen Eintrag. Beim Anlegen/
|
|
Bearbeiten eines Switches **oder Geräts** lässt sich per Dropdown ein
|
|
bestehender Eintrag auswählen oder über „+ Neue Zugangsdaten anlegen“ direkt
|
|
im selben Formular ein neuer anlegen, ohne vorher zur eigenen
|
|
**Zugangsdaten**-Seite wechseln zu müssen. Löschen ist nur möglich, wenn
|
|
weder ein Switch noch ein Gerät mehr auf den Eintrag verweist. Bestehende
|
|
Datenbanken aus einer älteren Version (Username/Passwort direkt am Switch)
|
|
werden beim ersten Start automatisch migriert.
|
|
|
|
Jede Zugangsdaten-Zeile hat außerdem eine explizite **Kategorie** (Switch /
|
|
Linux-Client / Windows-Client / Router-Sonstiges), vom Admin beim Anlegen
|
|
gesetzt — bewusst **nicht** automatisch per Ping/TTL-Heuristik erkannt (zu
|
|
unzuverlässig, siehe Diskussion in der Commit-Historie). Aktuell wertet nur
|
|
die Kategorie **Linux-Client** etwas aus: nur Geräte mit einer so
|
|
kategorisierten Zugangsdaten-Zuordnung erscheinen unter **Wartung** (siehe
|
|
unten). Windows/PowerShell-Wartung ist als eigener, separater Schritt
|
|
vorgesehen (kein Testsystem dafür verfügbar) — die Kategorie existiert
|
|
bereits, die Aktion dahinter noch nicht.
|
|
|
|
Jeder Switch kann außerdem einen individuellen **SSH-Port** hinterlegen
|
|
(Feld „SSH-Port“ beim Anlegen/Bearbeiten) — bleibt er leer, wird überall
|
|
automatisch **Port 22** angenommen (`SWITCH_DEFAULT_SSH_PORT` in `app.py`).
|
|
Der Port wird konsistent an allen drei Stellen verwendet, an denen die App
|
|
sich per SSH mit einem Switch verbindet: dem Web-Terminal (Verbindungstest),
|
|
`generate_ips.py` (liefert ihn als eigenes Feld an `poe.sh`) und den
|
|
`expect`-Aufrufen in `poe.sh` selbst (`ssh -p <port> ...`) für automatische
|
|
wie manuelle PoE-Neustarts.
|
|
|
|
## Wartung (SSH-Bulk-Update & -Neustart)
|
|
|
|
Eigene Seite unter **Geräte → Wartung** (eigenes Rechtepaar
|
|
`maintenance.view`/`maintenance.edit`), komplett getrennt vom PoE-Neustart
|
|
auf dem Dashboard:
|
|
|
|
- **Dashboard-Neustart** = immer PoE-Reset über den Switch (`poe.sh`),
|
|
funktioniert unabhängig davon, ob das Gerät SSH-Zugangsdaten hat.
|
|
- **Wartung** = immer eine direkte SSH-Verbindung zum Gerät selbst,
|
|
funktioniert unabhängig davon, ob/an welchem Switch es hängt.
|
|
|
|
Nur Geräte mit hinterlegten SSH-Zugangsdaten der Kategorie **Linux-Client**
|
|
(siehe oben) erscheinen in der Liste. Zwei Aktionen:
|
|
|
|
- **Update** (Mehrfachauswahl per Checkbox, ein Klick startet alle
|
|
ausgewählten parallel als eigene Hintergrund-Threads): führt serverseitig
|
|
`env DEBIAN_FRONTEND=noninteractive apt-get update && env
|
|
DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef"
|
|
-o Dpkg::Options::="--force-confold" upgrade` per SSH aus — nicht
|
|
interaktiv (kein Terminal, keine Rückfragemöglichkeit), die
|
|
`Dpkg::Options`-Flags verhindern, dass ein Paket-Postinst-Skript auf eine
|
|
Config-Datei-Rückfrage wartet, die nie kommt und sonst bis zum Timeout
|
|
hängen bliebe.
|
|
- **Neustart** (pro Gerät): `sudo reboot` per SSH. Ein abrupter
|
|
Verbindungsabbruch direkt danach ist erwartet (die Maschine fährt
|
|
herunter, bevor sie noch antworten kann) und zählt als Erfolg, kein
|
|
Fehler — nur ein Verbindungs-/Auth-Fehler **vor** dem eigentlichen
|
|
Neustart-Kommando gilt als echter Fehler.
|
|
|
|
Live-Status pro Gerät (läuft/erfolgreich/fehlgeschlagen inkl. Ausgabe der
|
|
letzten Aktion, per Klick ein-/ausblendbar) wird per Polling (`/maintenance/status`,
|
|
alle 2s solange etwas läuft, sonst alle 8s) aktualisiert, ohne die Seite neu
|
|
zu laden. Der Job-Status lebt bewusst nur im Prozessspeicher (wie beim
|
|
SSH-Terminal auch keine Sitzung persistiert wird) — ein Neustart von
|
|
`poe_web.service` verwirft nur die Anzeige, nicht die auf dem Zielgerät
|
|
bereits laufende Aktion selbst.
|
|
|
|
**Sicherheitsmodell**: dieselbe `known_hosts`-Datei wie beim
|
|
Browser-SSH-Terminal (`SSH_KNOWN_HOSTS_PATH`) — ein Host muss vorher
|
|
mindestens einmal interaktiv über „Verbindung testen“ bestätigt worden sein.
|
|
Anders als das interaktive Terminal (dort kann bei einem unbekannten
|
|
Host-Key wie bei einem echten `ssh`-Client live nachgefragt werden) läuft
|
|
hier nichts vor einem Menschen ab — ein unbekannter oder nicht vorher
|
|
bestätigter Host-Key wird deshalb per `paramiko.RejectPolicy` hart
|
|
abgelehnt statt automatisch akzeptiert, da ohne Rückfragemöglichkeit ein
|
|
automatisches Akzeptieren ein MITM-Risiko ohne jede Prüfung wäre.
|
|
|
|
Live gegen ein dediziertes Testsystem verifiziert (separat vom gemeinsam
|
|
genutzten App-Host, um dort kein echtes `apt upgrade` auszulösen): ein
|
|
echtes `apt update && apt upgrade` (inkl. Kernel-/systemd-/netplan-Paketen)
|
|
lief nicht-interaktiv vollständig durch (`status: success`, komplette
|
|
`apt`-Ausgabe im Job-Verlauf sichtbar), anschließend hat der SSH-Neustart
|
|
das Testsystem tatsächlich neu gestartet — bestätigt über `uptime -s`
|
|
direkt nach dem Job (Boot-Zeitpunkt stimmte mit dem Auslöse-Zeitpunkt
|
|
überein, nicht nur die vom Code erwartete Verbindungsunterbrechung).
|
|
|
|
Windows-Clients/PowerShell-Wartung ist bewusst **noch nicht** umgesetzt
|
|
(kein Testsystem verfügbar) — die Kategorie „Windows-Client“ existiert in
|
|
den Zugangsdaten bereits als Vorbereitung für einen späteren, separaten
|
|
Ausbauschritt.
|
|
|
|
## DHCP-Reservierungen & eigene Options (Systemeinstellungen → DHCP)
|
|
|
|
Erzeugt aus den vorhandenen Client-Stammdaten (MAC + IP + Name) automatisch
|
|
feste DHCP-Reservierungen inkl. eigener/herstellerspezifischer Options, ohne
|
|
die Daten ein zweites Mal pflegen zu müssen. Eigenes Rechtepaar
|
|
`settings_dhcp.view`/`settings_dhcp.edit`, gespiegelt in der Navbar unter
|
|
Einstellungen.
|
|
|
|
**Backend: Kea DHCP** (`kea-dhcp4-server`), nicht das ältere
|
|
`isc-dhcp-server` — ISC hat isc-dhcp-server 2022 als EOL erklärt, Kea ist
|
|
der aktiv weiterentwickelte Nachfolger und bildet „globaler Wert, pro Client
|
|
überschreibbar“ nativ ab (siehe unten).
|
|
|
|
- **Status-Erkennung** (rein lesend): `shutil.which("kea-dhcp4")` +
|
|
`systemctl is-active`/`is-enabled`. Zeigt Version, Lauf- und
|
|
Autostart-Status.
|
|
- **Installation & Dienststeuerung**: eigene, jeweils einzeln bestätigte
|
|
Buttons — „kea-dhcp4-server installieren“ (`apt-get install -y`),
|
|
„Aktivieren & (neu) starten“ (`systemctl enable` + `restart`) und
|
|
„Stoppen“ (`systemctl stop` **+ `disable`**, damit ein bewusst gestoppter
|
|
Dienst nach einem Host-Neustart nicht trotzdem automatisch wieder
|
|
hochfährt — ein Host-Neustart übernimmt so genau den zuletzt bewusst
|
|
gewählten Zustand, an oder aus). Bewusst **nicht** automatisch an
|
|
„Konfiguration speichern“ oder „In Datei schreiben“ gekoppelt — ein
|
|
unerwartet aktiver oder fehlkonfigurierter DHCP-Server kann ein ganzes
|
|
Netzsegment stören, jede dieser Aktionen soll ein bewusster, separater
|
|
Klick bleiben.
|
|
- **Mehrere Subnetze gleichzeitig**: ein Host kann mehrere IPs/Interfaces
|
|
mit jeweils eigenem Netz haben, für die alle DHCP angeboten werden soll
|
|
(`dhcp_subnets`-Tabelle statt einer einzelnen globalen Range) — jedes
|
|
Subnetz hat eigene Interface/Range sowie optional eigenes Gateway/DNS,
|
|
generiert wird ein eigener `subnet4`-Block je Subnetz in der Kea-Config.
|
|
Domain, Lease-Zeiten und Ausgabepfad bleiben global (Kea-weit gültig).
|
|
- **Netzwerk-Kopplung**: Subnet und Netzmaske werden **nie manuell
|
|
eingegeben**, sondern bei jeder Anzeige/Generierung live aus der
|
|
System-Netzwerkkonfiguration des jeweiligen Interfaces gelesen
|
|
(`_detect_interface_network()`/`_detect_interface_networks()`:
|
|
`ip -4 addr show dev <iface>` + `ip route show default`) — berücksichtigt
|
|
dabei ALLE IPv4-Adressen eines Interfaces, nicht nur die erste (ein
|
|
Interface kann mehrere IPs/Subnetze gleichzeitig tragen, z.B. Alias-IPs).
|
|
Die generierte Kea-Konfiguration ist dadurch immer an das Netz gekoppelt,
|
|
in dem der Host tatsächlich hängt, auch wenn sich dessen IP/Subnet mal
|
|
ändert.
|
|
- **Range ist Pflicht und muss zu einem echten Netz passen**: kein
|
|
vorausgefüllter Default für Range/DNS (nur Platzhaltertext) — ein
|
|
Subnetz ohne zu einem tatsächlich vorhandenen Netz passende Range wird
|
|
beim Anlegen/Bearbeiten abgelehnt (`_dhcp_matching_network()`), da Kea
|
|
ohne eine echte, physisch vorhandene IP im gewünschten Bereich keine
|
|
gültige Subnet-CIDR zugeordnet werden kann. Dieselbe Prüfung läuft
|
|
zusätzlich bei „In Datei schreiben“ (Subnetze, die inzwischen zu keinem
|
|
echten Netz mehr passen — z.B. nach einem Netzwechsel des Hosts — werden
|
|
übersprungen und in der Erfolgsmeldung benannt, statt die ganze Aktion
|
|
abzubrechen) und bei „Aktivieren & (neu) starten“ (verweigert den Start,
|
|
falls kein einziges gültiges Subnetz mehr existiert). Live auf einer
|
|
echten Fehlkonfiguration reproduziert und verifiziert.
|
|
- **Subnetze einzeln deaktivierbar**: ein Subnetz lässt sich ohne Löschen
|
|
pausieren (`enabled`-Flag) — wird dann bei „In Datei schreiben“ und
|
|
„Aktivieren & (neu) starten“ komplett ignoriert (kein Fehler, bewusst
|
|
kein "ungültig", da das Fehlen des Netzes hier Absicht ist), statt es
|
|
jedes Mal neu anlegen zu müssen.
|
|
- **DHCP-Options** (`dhcp_option_defs`/`dhcp_option_values`): eine kuratierte
|
|
Auswahl an **56 Standard-Optionen** (analog den „Vordefinierten Optionen“
|
|
eines Windows-DHCP-Servers — u.a. NTP-/WINS-/TFTP-/SMTP-Server, Boot-Datei,
|
|
Domain-Search, IP-Forwarding) ist automatisch vorbefüllt und **nicht
|
|
löschbar** (Backend lehnt es ab, UI zeigt statt eines Löschen-Buttons ein
|
|
Schloss-Symbol) — Router/DNS-Server/Domain-Name/Lease-Zeiten sind bewusst
|
|
ausgenommen, da dafür bereits eigene, dedizierte Felder existieren
|
|
(Subnetze bzw. globale Einstellungen oben); ebenso client-/
|
|
protokollinterne Optionen (message-type, parameter-request-list,
|
|
requested-address, ...), die serverseitig nicht sinnvoll setzbar sind.
|
|
„host-name“ (12) ist ebenfalls bewusst **nicht** enthalten (war es
|
|
zwischenzeitlich, wurde aber wieder entfernt): Kea beantwortet die
|
|
Host-Name-Anfrage eines Clients bei einer Reservierung bereits automatisch
|
|
über deren eigenes `hostname`-Feld (aus dem Namen der Reservierung
|
|
abgeleitet) — ein zusätzlicher `option-data`-Override für dieselbe Option
|
|
wäre laut Kea-Dokumentation eine Dopplung und Quelle für Widersprüche,
|
|
live an einer echten Reservierung mit beiden gleichzeitig gesetzten
|
|
Werten nachvollzogen. Alle verbleibenden Namen sind live gegen eine echte
|
|
Kea-2.4.1-Instanz verifiziert (`kea-dhcp4 -t`, jede Option einzeln
|
|
getestet). Standard-Optionen bekommen bewusst **kein eigenes
|
|
`option-def`** in der generierten Konfiguration — Kea kennt sie bereits
|
|
nativ, eine Neudefinition würde die eingebaute duplizieren; nur
|
|
zusätzlich angelegte, wirklich eigene/herstellerspezifische Options (z.B.
|
|
eine Terminal-Boot-URL) bekommen eines und bleiben frei löschbar.
|
|
In der Tabelle sind grundsätzlich nur **tatsächlich genutzte** Options
|
|
sichtbar (eigene immer, Standard-Optionen nur mit gesetztem globalen Wert)
|
|
— weitere Standard-Optionen kommen über ein Dropdown „+ Standard-Option
|
|
hinzufügen“ dazu, statt alle 56 dauerhaft als leere Zeilen anzuzeigen.
|
|
Jede Option hat einen **globalen** Wert sowie optional einen **Wert pro
|
|
Client** — ein Client-Override überschreibt den globalen Wert
|
|
ausschließlich für dieses eine Gerät (technisch: globale Werte landen im
|
|
Top-Level `option-data`, Overrides im `option-data` der jeweiligen
|
|
Kea-Reservierung, was Kea automatisch pro Client Vorrang geben lässt). Im
|
|
Reservierungs-Options-Dialog gilt dasselbe Prinzip: nur Options mit
|
|
gesetztem Override sind sichtbar, weitere kommen über ein Dropdown dazu.
|
|
Das ausgewählte Feld wird dabei direkt unter das Dropdown verschoben und
|
|
fokussiert, statt an seiner ursprünglichen Stelle irgendwo in der u.U.
|
|
langen Liste (bis zu 56 mögliche Options) zu erscheinen — sonst lag es
|
|
oft außerhalb des sichtbaren Modal-Ausschnitts und wirkte, als wäre nichts
|
|
passiert (live genau so reproduziert und behoben).
|
|
- **Reservierungen aus zwei Quellen**: automatisch für jedes aktive Gerät mit
|
|
gültiger MAC + IP (Client-Stammdaten dieser App) sowie zusätzlich **manuell
|
|
anlegbar** („Reservierung hinzufügen“, MAC/IP/Name) — für Geräte, die
|
|
nicht als Client gepflegt werden (z.B. ein externes Gerät), sodass ihnen
|
|
bewusst und mit voller Kontrolle eine feste IP gegeben werden kann, statt
|
|
sie unüberwacht automatisch anzubinden. Automatische Reservierungen können
|
|
in dieser Tabelle **nicht gelöscht** werden (Schloss-Symbol statt Button —
|
|
sie entstehen/verschwinden mit den Client-Stammdaten), manuelle schon. Bei
|
|
einer MAC in beiden Quellen gewinnt immer die automatische. Jede
|
|
Reservierung wird ins **passende Subnetz** einsortiert (die IP entscheidet,
|
|
zu welchem der ggf. mehreren konfigurierten Subnetze sie gehört); der
|
|
Hostname wird aus dem Namen abgeleitet (nur `a-z0-9-`, Kollisionen
|
|
innerhalb desselben Subnetzes bekommen automatisch ein `-2`/`-3`-Suffix).
|
|
Einträge ohne MAC/IP oder mit einer IP außerhalb aller konfigurierten
|
|
Subnetze werden mit Hinweis übersprungen — Kea lehnt eine Reservierung
|
|
außerhalb ihres Subnets als Konfigurationsfehler ab (mit einer echten
|
|
Kea-Instanz verifiziert, inkl. einer Mischung aus automatischer und
|
|
manueller Reservierung im selben Subnetz).
|
|
- **Aktive Leases**: eigene Karte, liest die tatsächlich vergebenen Adressen
|
|
direkt aus Keas Lease-Datenbank (`kea-leases4.csv`, memfile-Backend —
|
|
kein Control-Agent nötig, reiner Lesezugriff auf die Datei). Zeigt damit
|
|
auch Clients **ohne eigene Reservierung**, die sich einfach eine freie IP
|
|
aus dem Pool genommen haben — deutlich als „Ohne Reservierung“ markiert,
|
|
statt nur die (unvollständige) Reservierungsliste zu zeigen. Die
|
|
Ablaufzeit wird explizit als **UTC** ausgewiesen (Kea arbeitet intern in
|
|
UTC-Epoch) und zusätzlich als live tickender Countdown angezeigt — eine
|
|
reine Absolutzeit auf einer nicht selbst aktualisierenden Seite wirkte bei
|
|
den kurzen Standard-Lease-Zeiten (600s) schnell „schon abgelaufen“, sobald
|
|
etwas Zeit vergangen war, bis man tatsächlich hinschaut (live auf der
|
|
Test-VM als genau dieses Missverständnis reproduziert).
|
|
- **Schreiben**: „Speichern“ bei den Reservierungen (nur mit
|
|
`settings_dhcp.edit`) schreibt die vollständige, generierte Kea-JSON-
|
|
Konfiguration an den konfigurierten Ausgabepfad. Wirksam wird sie erst
|
|
nach einem Dienst-Neustart über den separaten Button.
|
|
- **Ampel im Topbar**: neben dem Prüfintervall-Timer zeigt ein grüner/roter
|
|
Punkt, ob der Kea-Dienst läuft — nur sichtbar mit `settings_dhcp.view`.
|
|
- Reservierungen mit aktiven eigenen Options zeigen in der Tabelle, welche
|
|
Option greift (global oder Client-Override).
|
|
|
|
Live gegen eine echte, frisch installierte Kea-Instanz verifiziert:
|
|
Installation, generierte Konfiguration besteht `kea-dhcp4 -t`, Dienststart
|
|
übernimmt sie fehlerfrei (`DHCP4_CONFIG_COMPLETE`), globaler Options-Wert
|
|
sowie Client-spezifischer Override erscheinen korrekt getrennt in der
|
|
Konfiguration.
|
|
|
|
Die Mehrfach-Subnetz-Unterstützung selbst (Anlegen/Bearbeiten/Löschen samt
|
|
Validierung, mehrere generierte `subnet4`-Blöcke mit korrekten Pools/
|
|
Interfaces, Migration einer bestehenden Einzel-Range) ist live auf einem
|
|
echten Host verifiziert. Zwei tatsächlich **unabhängige** Netze (z.B. über
|
|
zwei physische NICs) standen beim Test nicht zur Verfügung — verifiziert
|
|
wurde daher zusätzlich zwei Ranges innerhalb desselben real vorhandenen
|
|
Netzes (identischer `subnet`-CIDR, unterschiedliche Pools); ob Kea zwei
|
|
`subnet4`-Blöcke mit identischem `subnet`-Wert akzeptiert oder als Fehler
|
|
ablehnt, wurde nicht gegen eine echte Kea-Instanz geprüft — bei tatsächlich
|
|
unterschiedlichen Netzen (der eigentliche Anwendungsfall) besteht dieses
|
|
Risiko nicht. Die Leases-Anzeige wurde vollständig End-to-End im echten
|
|
Betrieb bestätigt: ein tatsächlicher, unreservierter Client im Testnetz
|
|
(„shellyhtg3-...“) hat sich selbstständig eine IP aus dem Pool geholt und
|
|
wurde korrekt mit der „Ohne Reservierung“-Markierung angezeigt.
|
|
|
|
## Netzwerkeinstellungen des Hosts (Systemeinstellungen)
|
|
|
|
Eigene, klar getrennte Funktion von obigem DHCP-*Server*: hier geht es um
|
|
die Netzwerkkonfiguration des Hosts, auf dem der PoE Manager selbst läuft
|
|
(IP-Adresse, DNS-Server, DHCP-Client vs. statisch, Hostname) — nicht um
|
|
Adressen, die er an andere Geräte vergibt. Der Hostname wird separat und
|
|
risikolos über `hostnamectl set-hostname` gesetzt (kein Revert-Timer nötig,
|
|
da eine Änderung nicht die Erreichbarkeit kappt).
|
|
|
|
- **Backend-Erkennung**: prüft, ob NetworkManager oder dhcpcd aktiv ist
|
|
(`systemctl is-active`); ist keins von beiden aktiv, aber `systemd-networkd`
|
|
aktiv und `netplan` installiert, wird **netplan** angenommen — der Standard
|
|
auf einem frisch installierten Ubuntu (Server wie Cloud-Image). Ist keins
|
|
der drei erkennbar, bleibt die Seite bewusst **rein lesend** (Status wird
|
|
trotzdem angezeigt) — ohne zuverlässige Erkennung des Backends keine
|
|
Änderung, um nicht ins Blaue zu raten.
|
|
- **Anzeige**: aktuelles Interface, IP/Prefix, Gateway, DNS-Server sowie
|
|
Modus (DHCP/Statisch) — Modus wird backend-spezifisch ermittelt
|
|
(`nmcli ipv4.method`, ein `static ip_address=`-Eintrag im betreffenden
|
|
`interface`-Block von `/etc/dhcpcd.conf`, bzw. bei netplan am `dynamic`-Flag
|
|
des Kernels auf der aktuellen Adresse — zuverlässiger als die u.U. mehreren
|
|
überlagernden YAML-Dateien unter `/etc/netplan/` selbst zu parsen).
|
|
- **Änderung**: Interface, Modus (DHCP/Statisch), bei Statisch zusätzlich
|
|
IP/Prefix/Gateway, sowie unabhängig davon eigene DNS-Server (funktioniert
|
|
in beiden Modi — z.B. IP per DHCP, aber feste DNS-Server). Bei netplan
|
|
landet die Änderung in einer eigenen, niedrig priorisierten Override-Datei
|
|
(`/etc/netplan/90-poe-manager.yaml`) statt in der vom Distributor bzw.
|
|
cloud-init mitgelieferten — netplan wendet Dateien alphabetisch an, spätere
|
|
Werte gewinnen pro Schlüssel, damit bleibt die Original-Datei unangetastet.
|
|
- **Sicherheitsnetz** (wie `netplan try`): vor jeder Änderung wird die
|
|
vorherige Konfiguration gesichert. Nach dem Anwenden läuft ein
|
|
Hintergrund-Timer (Standard 45s) — wird die Änderung nicht **explizit
|
|
bestätigt** (nur möglich, wenn die Seite über die neue Konfiguration noch
|
|
erreichbar ist), rollt die App automatisch auf die gesicherte
|
|
Konfiguration zurück. Verhindert, dass ein Tippfehler bei IP/Gateway den
|
|
Host von der Web-UI/SSH abschneidet.
|
|
- **DHCP-Server wird vorsorglich gestoppt UND deaktiviert**: eine vorher
|
|
passende Range (siehe DHCP-Konfiguration oben) kann nach einer
|
|
IP-/Subnetzänderung plötzlich zum falschen Netz gehören. Läuft Kea beim
|
|
Anwenden oder beim Auto-Rollback, wird der Dienst deshalb gestoppt und
|
|
deaktiviert (nicht automatisch neu gestartet — die Range muss der Admin
|
|
aktiv auf der DHCP-Seite prüfen und den Dienst dort bewusst wieder
|
|
starten). Das zusätzliche Deaktivieren verhindert, dass ein Host-Neustart
|
|
vor dieser Prüfung den Dienst trotzdem automatisch mit der ggf. falschen
|
|
Range wieder hochfährt, statt dass die App mit einer möglicherweise
|
|
falschen Konfiguration weiterläuft oder blind rät.
|
|
- Live gegen eine echte Ubuntu-24.04-VM verifiziert (Standard-Installation,
|
|
Netz per DHCP über netplan/systemd-networkd bezogen) — sowohl die
|
|
Erkennung/Statusanzeige als auch der komplette Anwenden-Pfad inkl. der
|
|
eigenen Override-Datei. NetworkManager und dhcpcd sind zusätzlich per
|
|
gemockten `subprocess.run`/Dateizugriffen verifiziert (inkl. eines echten
|
|
`dhcpcd.conf`-Rewrites und eines vollständigen Anwenden-dann-Auto-
|
|
Rollback-Durchlaufs), da in der WSL-Entwicklungsumgebung keins von beiden
|
|
verfügbar ist.
|
|
|
|
## Änderungslog
|
|
|
|
Jede Anlage, Bearbeitung, Löschung sowie jedes Aktivieren/Deaktivieren von
|
|
Geräten, Switchen, Zugangsdaten, Benutzern und Gruppen wird in der Tabelle
|
|
`audit_log` protokolliert (Zeitpunkt, Benutzer, Aktion, Ziel, Details) —
|
|
einsehbar unter **Logs → Änderungen** (Recht `logs_activity.view`). Zusätzlich merken
|
|
sich Geräte und Switche direkt am Datensatz (`last_modified_by`,
|
|
`last_modified_at`), wer sie zuletzt geändert hat, damit man das nicht erst
|
|
im Log nachschlagen muss.
|
|
|
|
PoE-Neustarts werden **bewusst nicht** im Änderungslog erfasst — die stehen
|
|
bereits im Live-Log von `poe.sh`. Eine Ausnahme ist die Änderung des
|
|
Prüfintervalls selbst (eine Einstellungsänderung, kein Neustart), die wird
|
|
mitprotokolliert.
|
|
|
|
## Import/Export
|
|
|
|
Unter **Einstellungen** lassen sich Geräte, Switche und Zugangsdaten
|
|
(inkl. entschlüsselter Passwörter) als ein einziges JSON-Bundle exportieren.
|
|
Das Bundle wird mit einem aus einer selbstgewählten Passphrase abgeleiteten
|
|
Schlüssel verschlüsselt (PBKDF2-HMAC-SHA256 + Fernet) — unabhängig vom
|
|
`fernet.key` dieser Installation, damit die Datei auf einer komplett neuen
|
|
Umgebung (mit eigenem, neu generiertem `fernet.key`) wieder eingelesen
|
|
werden kann. Gedacht für einen Umzug auf einen neuen Server.
|
|
|
|
Beim Import wird dieselbe Passphrase erneut abgefragt; Zugangsdaten werden
|
|
nach Name, Switche nach Hostname und Geräte nach MAC-Adresse abgeglichen —
|
|
bestehende Einträge mit gleichem Schlüssel werden aktualisiert, neue werden
|
|
angelegt. Switche referenzieren ihre Zugangsdaten dabei über den Namen (nicht
|
|
die interne ID), damit der Import auch bei abweichender ID-Vergabe auf dem
|
|
Zielsystem korrekt verknüpft.
|
|
|
|
## Eigenes Konto & Profilbild
|
|
|
|
Das Zahnrad-Symbol neben dem eigenen Namen unten in der Sidebar führt zu
|
|
**„Mein Konto“** (`/account`): Vor-/Nachname ändern, ein Profilbild
|
|
hochladen (PNG/JPG/GIF/WEBP) und das eigene Passwort ändern (erfordert
|
|
Eingabe des aktuellen Passworts). Das ist bewusst getrennt vom Admin-Bereich
|
|
„Benutzer“, der andere Konten verwaltet. Ist ein Profilbild hinterlegt,
|
|
erscheint es statt der Initialen sowohl in der Sidebar als auch im
|
|
Änderungslog neben dem Benutzernamen. Admins sehen auf derselben Seite
|
|
zusätzlich die Navbar-Reihenfolge (siehe unten).
|
|
|
|
## Warnung bei ungespeicherten Änderungen
|
|
|
|
Generischer, app-weiter Mechanismus (kein Opt-in pro Seite nötig): erkennt
|
|
automatisch, ob ein Formular mit echten Eingabefeldern (nicht nur
|
|
versteckten Aktions-Feldern wie bei Löschen-/Toggle-Buttons) seit dem Laden
|
|
der Seite verändert wurde, und fragt beim Verlassen (Klick auf einen Link,
|
|
z.B. in der Navigation) per eigenem **Ja/Nein-Dialog**, ob zuerst gespeichert
|
|
werden soll, statt Änderungen stillschweigend zu verwerfen:
|
|
|
|
- **„Ja, speichern“**: sendet das betroffene Formular ganz normal ab (echter
|
|
POST, kein AJAX-Nachbau) und merkt sich das eigentliche Navigationsziel in
|
|
`sessionStorage`, um nach dem Speichern automatisch dorthin
|
|
weiterzuleiten — der Link muss dafür nicht ein zweites Mal angeklickt
|
|
werden.
|
|
- **„Nein, verwerfen“**: verwirft die Änderungen und wechselt direkt zur
|
|
Zielseite.
|
|
- Klickt man stattdessen auf das eigene „Speichern“/„Anlegen“ der Seite
|
|
direkt (ohne über einen Link zu einer anderen Seite zu gehen), passiert
|
|
**nichts zusätzlich** — kein Dialog, kein Browser-Popup, es wird einfach
|
|
ganz normal gespeichert.
|
|
- Für Browser-eigene Navigation (Tab schließen, Neuladen, Adresszeile) gibt
|
|
es zusätzlich `beforeunload` — dort erlaubt der Browser aus
|
|
Sicherheitsgründen nur eine generische Warnung ohne eigene Buttons, kein
|
|
Ja/Nein/Speichern-Dialog. Sie wird bewusst unterdrückt, sobald die Seite
|
|
ohnehin schon bewusst verlassen wird (eigenes Speichern oder der
|
|
Ja/Nein-Dialog) — sie soll ausschließlich bei tatsächlich ungefragter
|
|
Navigation erscheinen, nicht zusätzlich bei einem ganz normalen Speichern
|
|
(live als genau dieses Doppel-Popup reproduziert und behoben).
|
|
- Ausnahmen über `data-no-unsaved-guard` auf Formular oder Link (z.B. das
|
|
Login-Formular — Zugangsdaten eintippen ist keine "Einstellung").
|
|
|
|
Live per Playwright auf mehreren Seiten (Systemeinstellungen, DHCP-Options,
|
|
Gruppen anlegen) verifiziert: eigener Dialog erscheint nur bei tatsächlich
|
|
ungespeicherten Änderungen, „Ja“ speichert und leitet danach automatisch
|
|
zum ursprünglichen Ziel weiter, „Nein“ verwirft und wechselt direkt, ein
|
|
normales Speichern über den Seiten-eigenen Button zeigt weder eigenen noch
|
|
Browser-Dialog.
|
|
|
|
Nebenbei wurde ein generisches Cache-Busting für `app.js`/`style.css`
|
|
ergänzt (`asset_url()`-Helper, hängt die Änderungszeit der Datei als
|
|
`?v=...` an) — vorher konnte ein bereits gecachtes, veraltetes `app.js` im
|
|
Browser nach einem Update scheinbar "nicht funktionierende" neue Funktionen
|
|
verursachen, obwohl der Code auf dem Server längst aktuell war.
|
|
|
|
## Navigation anpassen
|
|
|
|
Die Sidebar ist in drei aufklappbare Gruppen strukturiert:
|
|
|
|
- **Geräte** — Clients (die eigentlichen Devices), Switche, Zugangsdaten,
|
|
Wartung (SSH-Bulk-Update/-Neustart)
|
|
- **Einstellungen** — Benutzer, Gruppen, Systemeinstellungen (Prüfintervall),
|
|
Im-/Export
|
|
- **Logs** — Live, Änderungen (Änderungslog)
|
|
|
|
Die Reihenfolge dieser drei Gruppen (und von „Dashboard“) ist für Admins
|
|
unter **Mein Konto** (Zahnrad-Symbol in der Sidebar) per Auf-/Ab-Buttons
|
|
konfigurierbar (gespeichert als JSON-Liste unter `settings.nav_order`) und
|
|
gilt global für alle Benutzer. Unabhängig von der Reihenfolge sieht jeder
|
|
Benutzer ohnehin nur, wofür er berechtigt ist — sowohl auf Gruppenebene als
|
|
auch pro Unterpunkt: hat ein Benutzer z.B. nur `devices.view`, erscheint die
|
|
Gruppe „Geräte“ nur mit dem Unterpunkt „Clients“ (ohne Switche/
|
|
Zugangsdaten); eine Gruppe verschwindet komplett, wenn kein Unterpunkt
|
|
sichtbar ist.
|
|
|
|
## Entwicklung unter WSL
|
|
|
|
Da das Projekt ausschließlich für Linux gebaut ist, empfiehlt sich die
|
|
Entwicklung direkt unter WSL2 (Ubuntu), auch wenn der Editor unter Windows
|
|
läuft — das Projektverzeichnis liegt dabei ganz normal unter
|
|
`C:\...\aruba-poe-modern` und wird aus WSL über `/mnt/c/...` erreicht:
|
|
|
|
```bash
|
|
wsl --install -d Ubuntu # einmalig, falls noch nicht vorhanden
|
|
wsl -d Ubuntu
|
|
sudo apt update && sudo apt install -y python3 python3-venv python3-pip sqlite3 expect openssh-client
|
|
cd /mnt/c/Users/<dich>/aruba-poe-modern/srv/poe_manager
|
|
python3 -m venv venv
|
|
./venv/bin/pip install -r requirements.txt
|
|
./venv/bin/python create_db.py
|
|
./venv/bin/python create_admin.py
|
|
./venv/bin/python app.py # http://localhost:5000 — via WSL2-Portweiterleitung
|
|
# auch aus dem Windows-Browser erreichbar
|
|
```
|
|
|
|
**Hinweis zu `rpi-check.service`:** Der „Speichern & Service neustarten“-Button
|
|
unter Systemeinstellungen sowie das „Jetzt prüfen“-Icon funktionieren nur,
|
|
wenn `rpi-check.service` tatsächlich als echter systemd-Service läuft (viele
|
|
aktuelle WSL2-Installationen unterstützen systemd, siehe `wsl.conf`
|
|
`[boot] systemd=true`). Für lokale Tests reicht es, `etc/systemd/system/
|
|
rpi-check.service` sowie `usr/local/bin/custom/poe.sh` per Symlink an ihre
|
|
Produktivpfade (`/etc/systemd/system/`, `/usr/local/bin/custom/`) zu legen
|
|
und `/srv/poe_manager` auf den Checkout zu verlinken, dann `systemctl
|
|
daemon-reload && systemctl enable --now rpi-check.service`. Ohne systemd
|
|
(z.B. `systemctl` nicht vorhanden) scheitert der Neustart-Aufruf lautlos —
|
|
das Intervall in der DB wird trotzdem aktualisiert, greift dann aber erst
|
|
nach einem manuellen Neustart des Prozesses.
|
|
|
|
## Lokal starten (Entwicklung)
|
|
|
|
```bash
|
|
cd srv/poe_manager
|
|
python3 -m venv venv
|
|
source venv/bin/activate
|
|
pip install -r requirements.txt
|
|
|
|
python create_db.py # legt sqlite.db + Tabellen an
|
|
python create_admin.py # legt ersten Admin-Benutzer an
|
|
|
|
python app.py # http://localhost:5000
|
|
```
|
|
|
|
## Produktiv-Deployment (auf der Ziel-VM, App landet unter `/srv/poe_manager`)
|
|
|
|
Das Repo wird bewusst **nicht** direkt nach `/srv/poe_manager` geklont —
|
|
`install.sh` kopiert (`rsync`) den Inhalt von `srv/poe_manager/` aus dem Repo
|
|
gezielt dorthin und lässt Quell-Checkout und Zielverzeichnis damit sauber
|
|
getrennt (kein doppeltes `/srv/poe_manager/srv/poe_manager/…`):
|
|
|
|
```bash
|
|
sudo git clone <dieses-repo> /opt/aruba-poe-modern-src
|
|
cd /opt/aruba-poe-modern-src
|
|
sudo bash install.sh
|
|
sudo /srv/poe_manager/venv/bin/python3 /srv/poe_manager/create_admin.py
|
|
```
|
|
|
|
Nach der Installation liegt die laufende App vollständig unter
|
|
**`/srv/poe_manager`** (so wie es `poe_web.service`, `rpi-check.service` und
|
|
`POE_SCRIPT`/`POE_DB_PATH` als Default erwarten) — `/opt/aruba-poe-modern-src`
|
|
wird nur als Checkout-Quelle für `install.sh` gebraucht und kann danach
|
|
liegen bleiben (für spätere Updates: `git pull` dort, dann `install.sh`
|
|
erneut ausführen) oder gelöscht werden.
|
|
|
|
`install.sh` installiert Systempakete, richtet die venv ein, initialisiert die
|
|
Datenbank (falls nicht vorhanden), kopiert `poe.sh` nach
|
|
`/usr/local/bin/custom/`, registriert die systemd-Units
|
|
(`poe_web.service`, `rpi-check.service`, `rpi-check-restart.timer`) und
|
|
konfiguriert nginx als Reverse-Proxy auf Port 80.
|
|
|
|
### Relevante Umgebungsvariablen (optional)
|
|
|
|
| Variable | Zweck | Default |
|
|
|-----------------------|------------------------------------------|---------------------------------------|
|
|
| `POE_DB_PATH` | Pfad zur SQLite-DB | `<app-dir>/sqlite.db` |
|
|
| `POE_SCRIPT` | Pfad zum PoE-Restart-Skript | `/usr/local/bin/custom/poe.sh` |
|
|
| `POE_LOG_GLOB` | Glob-Pattern der Check-Logs | `/var/log/rpi-*.log` |
|
|
| `POE_FERNET_KEY` | Pfad zum Fernet-Schlüssel | `<app-dir>/fernet.key` (auto-erzeugt)|
|
|
| `POE_KNOWN_HOSTS` | Pfad zur SSH-known_hosts-Datei des Terminals| `<app-dir>/known_hosts` (auto-erzeugt)|
|
|
| `POE_SECRET_KEY` | Flask Secret Key (überschreibt Datei) | zufällig, in `secret.key` gespeichert|
|
|
| `PORT`, `FLASK_DEBUG` | Flask-Serverport / Debug-Modus | `5000` / aus |
|
|
|
|
## Browser-SSH-Terminal (Switch-Verbindungstest)
|
|
|
|
„Verbindung testen“ im Switch-Modal öffnet eine WebSocket-Verbindung zu
|
|
`/ws/ssh_terminal`. Serverseitig baut **paramiko** eine echte SSH-Sitzung
|
|
zum Switch auf (inkl. interaktiver Host-Key-Bestätigung beim Erstkontakt,
|
|
danach dauerhaft in `known_hosts` gemerkt) und reicht eine interaktive Shell
|
|
1:1 an ein xterm.js-Terminal im Browser durch. Die Zugangsdaten aus dem
|
|
Formular werden direkt für die SSH-Authentifizierung verwendet (nicht als
|
|
Tastatureingabe in die Shell getippt) — Erfolg/Fehlschlag der Anmeldung wird
|
|
sofort und eindeutig im Terminal angezeigt. Ein Klick auf „Passwort
|
|
einfügen“ sendet den aktuell im Formular eingetragenen Wert zusätzlich als
|
|
Tastatureingabe (z.B. für ein `sudo`-Passwort in der geöffneten Sitzung).
|
|
|
|
Voraussetzungen:
|
|
- Läuft auf **jedem Betriebssystem** (paramiko ist reines Python) — die
|
|
SSH-Verbindung wird dabei immer vom PoE-Server aus aufgebaut, nie vom
|
|
Browser/Client.
|
|
- Nur für eingeloggte **Admins** erreichbar.
|
|
- Der Flask-Entwicklungsserver muss mit `threaded=True` laufen (ist bereits
|
|
so konfiguriert) — für einen echten Produktivbetrieb empfiehlt sich
|
|
ohnehin ein WSGI-Server mit WebSocket-Unterstützung (z.B. Gunicorn mit
|
|
`gevent`- oder `eventlet`-Worker) statt des eingebauten Dev-Servers.
|
|
- **nginx muss den WebSocket-Upgrade für `/ws/` durchreichen** (`Upgrade`/
|
|
`Connection`-Header, siehe `etc/nginx/sites-available/poe_manager`) — ohne
|
|
das lehnt Flask den Handshake mit `400 Bad Request` ab und das Terminal
|
|
bleibt leer auf „Getrennt“ hängen, obwohl derselbe Aufruf direkt gegen
|
|
Port 5000 (an nginx vorbei) funktioniert. War auf einer frisch per
|
|
`install.sh` aufgesetzten Ubuntu-VM live reproduzierbar, da alle bisherigen
|
|
Terminal-Tests ausschließlich direkt gegen Flask liefen, nie durch nginx.
|
|
- Da `poe_web.service` (wie im Original) als `root` läuft, kann über dieses
|
|
Terminal im Prinzip jeder erreichbare Host per SSH kontaktiert werden —
|
|
bewusst so gewählt, da Admins über die App ohnehin schon vollen Zugriff auf
|
|
alle hinterlegten Switch-Zugangsdaten haben. Für eine strengere Trennung
|
|
empfiehlt es sich, `poe_web.service` künftig unter einem eigenen,
|
|
unprivilegierten Systembenutzer laufen zu lassen (im Original wie auch hier
|
|
aktuell `root`, um Änderungen am produktiven Setup nicht zu vergrößern).
|
|
|
|
## Projektstruktur
|
|
|
|
```
|
|
srv/poe_manager/ Flask-App (Backend + Templates + Static-Assets)
|
|
usr/local/bin/custom/ poe.sh — Ping-Check & PoE-Restart per SSH (expect)
|
|
etc/systemd/system/ Service-/Timer-Units (Web-App + Check-Dienst)
|
|
etc/nginx/ Reverse-Proxy-Konfiguration
|
|
install.sh Produktiv-Installer
|
|
```
|