bcrypt==5.0.0
blinker==1.9.0
cffi==2.0.0
click==8.3.0
cryptography==46.0.1
Flask==3.1.2
Flask-Bcrypt==1.0.1
Flask-Login==0.6.3
Flask-Sock==0.7.0
gunicorn==23.0.0
itsdangerous==2.2.0
Jinja2==3.1.6
ldap3==2.9.1
MarkupSafe==3.0.2
paramiko==3.5.1
pyasn1==0.6.4
pycparser==2.23
PyNaCl==1.5.0
PyYAML==6.0.2
simple-websocket==1.1.0
Werkzeug==3.1.3

# Hinweis: Das Browser-SSH-Terminal (Switch-Verbindungstest) nutzt paramiko
# (reines Python) statt eines lokalen pty + System-ssh-Client und läuft
# damit plattformunabhängig — auch unter Windows. Die SSH-Verbindung wird
# dabei immer serverseitig (vom PoE-Server) aufgebaut, nie vom Browser aus.
#
# PyYAML wird für das netplan-Backend der Host-Netzwerkeinstellungen
# gebraucht (Systemeinstellungen → Netzwerkeinstellungen) — schreibt/liest
# die eigene Override-Datei unter /etc/netplan/.
#
# ldap3 (reines Python, keine System-Bibliothek wie libldap nötig) für die
# optionale Active-Directory/LDAP-Anmeldung (Systemeinstellungen → LDAP) —
# Search+Bind gegen einen AD-Domain-Controller oder generischen LDAP-Server.
#
# gunicorn: produktiver WSGI-Server für tesm.service (siehe dort) statt
# Flasks eigenem app.run()-Entwicklungsserver ("WARNING: This is a
# development server..."). Läuft bewusst mit genau EINEM Worker-Prozess,
# aber mehreren Threads (--worker-class gthread) -- mehrere Prozesse
# würden den In-Memory-Zustand für laufende Wartungs-Jobs/Import-
# Vorschauen (_maintenance_jobs/_pending_imports/...) auf mehrere,
# voneinander isolierte Prozesse verteilen, wodurch eine Anfrage, die vom
# jeweils ANDEREN Worker beantwortet wird, diesen Zustand nie sehen würde.
# Mehrere Threads (statt eines async-Workers) sind nötig, weil das
# Browser-SSH-Terminal (flask-sock) pro offener WebSocket-Verbindung einen
# blockierenden Thread belegt.
