Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b3ce96678 |
No files matched your search
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm"
|
name = "tesm"
|
||||||
version = "2.8.19"
|
version = "2.8.20"
|
||||||
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ VERBINDUNGSLIMIT = 6.0
|
|||||||
#: bis ein Schub weitergereicht wird.
|
#: bis ein Schub weitergereicht wird.
|
||||||
BLOCK = 32 * 1024
|
BLOCK = 32 * 1024
|
||||||
|
|
||||||
|
#: Wie oft nachgesehen wird, ob die Sitzung noch etwas tut. Kurz genug, dass
|
||||||
|
#: das Trennen puenktlich ist, lang genug, dass das Nachsehen nichts kostet.
|
||||||
|
TAKT = 1.0
|
||||||
|
|
||||||
|
|
||||||
def _geraet(conn: Any, device_id: int) -> dict[str, Any] | None:
|
def _geraet(conn: Any, device_id: int) -> dict[str, Any] | None:
|
||||||
device = inventory.get_device(conn, device_id)
|
device = inventory.get_device(conn, device_id)
|
||||||
@@ -231,14 +235,19 @@ def _strecke(ws, device_id: int) -> None: # type: ignore[no-untyped-def]
|
|||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
ws.close(1011, f"{host}:{port} antwortet nicht ({exc}).")
|
ws.close(1011, f"{host}:{port} antwortet nicht ({exc}).")
|
||||||
return
|
return
|
||||||
# Das Zeitlimit galt dem *Aufbau* -- danach muss es weg.
|
# Das Zeitlimit galt dem *Aufbau* -- als Abbruchgrund taugt es nicht.
|
||||||
#
|
#
|
||||||
# ``create_connection`` setzt es an den Socket, und dort gilt es fuer jede
|
# ``create_connection`` setzt es an den Socket, und dort gilt es fuer jede
|
||||||
# weitere Leseoperation. Ein Bildschirm, auf dem sich gerade nichts tut,
|
# weitere Leseoperation. Ein Bildschirm, auf dem sich gerade nichts tut,
|
||||||
# schickt sekundenlang nichts; nach sechs Sekunden Stille brach das Lesen
|
# schickt sekundenlang nichts; nach sechs Sekunden Stille brach das Lesen
|
||||||
# mit einer Zeitueberschreitung ab, und die Sitzung war weg. Genau so
|
# mit einer Zeitueberschreitung ab, und die Sitzung war weg. Genau so
|
||||||
# beobachtet: Bild steht, nach fuenf bis zehn Sekunden Schluss.
|
# beobachtet: Bild steht, nach fuenf bis zehn Sekunden Schluss.
|
||||||
gegenstelle.settimeout(None)
|
#
|
||||||
|
# Stattdessen ein kurzer Takt: das Lesen laeuft immer wieder in ein
|
||||||
|
# Zeitlimit, und *das* ist kein Abbruch, sondern der Anlass nachzusehen,
|
||||||
|
# ob die Sitzung ueberhaupt noch etwas tut.
|
||||||
|
gegenstelle.settimeout(TAKT)
|
||||||
|
leerlauf = int(extension.settings.get(conn, "vnc_idle_timeout") or 0)
|
||||||
|
|
||||||
# Ins **laufende** Protokoll, nicht in die Aenderungskette: eine
|
# Ins **laufende** Protokoll, nicht in die Aenderungskette: eine
|
||||||
# Bildschirmsitzung aendert nichts an der Anlage. Das Aenderungsprotokoll
|
# Bildschirmsitzung aendert nichts an der Anlage. Das Aenderungsprotokoll
|
||||||
@@ -253,21 +262,64 @@ def _strecke(ws, device_id: int) -> None: # type: ignore[no-untyped-def]
|
|||||||
|
|
||||||
laeuft = threading.Event()
|
laeuft = threading.Event()
|
||||||
laeuft.set()
|
laeuft.set()
|
||||||
|
#: Wann zuletzt etwas floss -- in *irgendeine* Richtung. Eine Liste, weil
|
||||||
|
#: beide Richtungen daran schreiben und eine Zuweisung in Python nicht in
|
||||||
|
#: die andere hineinlaufen kann.
|
||||||
|
zuletzt = [time.monotonic()]
|
||||||
|
#: Grund des Endes, falls wir selbst getrennt haben.
|
||||||
|
schluss = [""]
|
||||||
|
#: Kam ueberhaupt schon ein Byte vom Geraet?
|
||||||
|
gehoert = [False]
|
||||||
|
|
||||||
def vom_geraet() -> None:
|
def vom_geraet() -> None:
|
||||||
"""Alles, was der VNC-Server schickt, geht als Binaerrahmen weiter."""
|
"""Alles, was der VNC-Server schickt, geht als Binaerrahmen weiter."""
|
||||||
try:
|
try:
|
||||||
while laeuft.is_set():
|
while laeuft.is_set():
|
||||||
daten = gegenstelle.recv(BLOCK)
|
try:
|
||||||
|
daten = gegenstelle.recv(BLOCK)
|
||||||
|
except socket.timeout:
|
||||||
|
# Kein Fehler, nur Stille -- und die bedeutet zweierlei,
|
||||||
|
# je nachdem, ob die Sitzung schon stand.
|
||||||
|
#
|
||||||
|
# **Vor dem ersten Byte** ist Stille ein Befund: der Port
|
||||||
|
# nimmt an, aber es kommt nichts. Genau so verhaelt sich
|
||||||
|
# ein RealVNC Server, der die Adresse gerade aussperrt. Wer
|
||||||
|
# das nicht erfaehrt, sieht ewig "Verbindung wird
|
||||||
|
# aufgebaut" -- und sucht den Fehler bei uns.
|
||||||
|
if not gehoert[0] and time.monotonic() - begonnen > VERBINDUNGSLIMIT:
|
||||||
|
schluss[0] = (
|
||||||
|
f"{host}:{port} nimmt die Verbindung an, schickt aber nichts. "
|
||||||
|
"So verhält sich ein VNC-Server, der die Adresse gerade "
|
||||||
|
"aussperrt (RealVNC: BlacklistTimeout) – ein Neustart des "
|
||||||
|
"VNC-Dienstes auf dem Gerät räumt das ab."
|
||||||
|
)
|
||||||
|
break
|
||||||
|
# **Danach** ist Stille der Normalfall: ein Bildschirm, auf
|
||||||
|
# dem sich nichts tut, schickt nichts.
|
||||||
|
if gehoert[0] and leerlauf and time.monotonic() - zuletzt[0] > leerlauf:
|
||||||
|
schluss[0] = (
|
||||||
|
f"Sitzung nach {leerlauf} Sekunden ohne Bewegung getrennt"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
continue
|
||||||
if not daten:
|
if not daten:
|
||||||
|
if not gehoert[0]:
|
||||||
|
schluss[0] = (
|
||||||
|
f"{host}:{port} hat die Verbindung sofort wieder geschlossen."
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
gehoert[0] = True
|
||||||
|
zuletzt[0] = time.monotonic()
|
||||||
ws.send(daten)
|
ws.send(daten)
|
||||||
except Exception: # noqa: BLE001 - Abbruch ist der Normalfall, nicht die Stoerung
|
except Exception: # noqa: BLE001 - Abbruch ist der Normalfall, nicht die Stoerung
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
laeuft.clear()
|
laeuft.clear()
|
||||||
try:
|
try:
|
||||||
ws.close()
|
if schluss[0]:
|
||||||
|
ws.close(1000 if gehoert[0] else 1011, schluss[0])
|
||||||
|
else:
|
||||||
|
ws.close()
|
||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -284,6 +336,7 @@ def _strecke(ws, device_id: int) -> None: # type: ignore[no-untyped-def]
|
|||||||
# waere entweder ein Irrtum oder ein Versuch -- beides endet
|
# waere entweder ein Irrtum oder ein Versuch -- beides endet
|
||||||
# hier und nicht auf dem Geraet.
|
# hier und nicht auf dem Geraet.
|
||||||
continue
|
continue
|
||||||
|
zuletzt[0] = time.monotonic()
|
||||||
gegenstelle.sendall(nachricht)
|
gegenstelle.sendall(nachricht)
|
||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
logger.debug("Bildschirmsitzung beendet (%s)", device["name"], exc_info=True)
|
logger.debug("Bildschirmsitzung beendet (%s)", device["name"], exc_info=True)
|
||||||
@@ -298,5 +351,6 @@ def _strecke(ws, device_id: int) -> None: # type: ignore[no-untyped-def]
|
|||||||
if live is not None:
|
if live is not None:
|
||||||
live.info(
|
live.info(
|
||||||
"vnc",
|
"vnc",
|
||||||
f"Bildschirmsitzung {device['name']} beendet ({dauer}s)",
|
f"Bildschirmsitzung {device['name']} beendet ({dauer}s)"
|
||||||
|
+ (f" – {schluss[0]}" if schluss[0] else ""),
|
||||||
)
|
)
|
||||||
@@ -105,6 +105,7 @@ def system():
|
|||||||
"dashboard_public",
|
"dashboard_public",
|
||||||
"vnc_enabled",
|
"vnc_enabled",
|
||||||
"vnc_port",
|
"vnc_port",
|
||||||
|
"vnc_idle_timeout",
|
||||||
"vnc_warn_unencrypted_off",
|
"vnc_warn_unencrypted_off",
|
||||||
"terminal_switches_enabled",
|
"terminal_switches_enabled",
|
||||||
"terminal_clients_enabled",
|
"terminal_clients_enabled",
|
||||||
@@ -148,6 +149,7 @@ def system_save():
|
|||||||
"dashboard_public",
|
"dashboard_public",
|
||||||
"vnc_enabled",
|
"vnc_enabled",
|
||||||
"vnc_port",
|
"vnc_port",
|
||||||
|
"vnc_idle_timeout",
|
||||||
"vnc_warn_unencrypted_off",
|
"vnc_warn_unencrypted_off",
|
||||||
"terminal_switches_enabled",
|
"terminal_switches_enabled",
|
||||||
"terminal_clients_enabled",
|
"terminal_clients_enabled",
|
||||||
|
|||||||
@@ -66,6 +66,18 @@ TESM_SETTINGS: tuple[SettingSpec, ...] = (
|
|||||||
minimum=1,
|
minimum=1,
|
||||||
maximum=65535,
|
maximum=65535,
|
||||||
),
|
),
|
||||||
|
SettingSpec(
|
||||||
|
"vnc_idle_timeout",
|
||||||
|
300,
|
||||||
|
"int",
|
||||||
|
"Bildschirmsitzung bei Untätigkeit trennen nach (Sekunden)",
|
||||||
|
hint="Gezählt wird, wenn in **keine** Richtung mehr etwas fließt: der Bildschirm "
|
||||||
|
"ändert sich nicht und es kommt keine Eingabe. Eine Sitzung, die jemand offen "
|
||||||
|
"vergisst, hält sonst die Verbindung zum Gerät. 0 schaltet die Trennung ab.",
|
||||||
|
section="monitoring",
|
||||||
|
minimum=0,
|
||||||
|
maximum=86400,
|
||||||
|
),
|
||||||
SettingSpec(
|
SettingSpec(
|
||||||
"vnc_warn_unencrypted_off",
|
"vnc_warn_unencrypted_off",
|
||||||
False,
|
False,
|
||||||
|
|||||||
@@ -46,17 +46,6 @@
|
|||||||
{{ checkbox('dashboard_public', 'Statusübersicht ohne Anmeldung erreichbar',
|
{{ checkbox('dashboard_public', 'Statusübersicht ohne Anmeldung erreichbar',
|
||||||
checked=values.dashboard_public,
|
checked=values.dashboard_public,
|
||||||
hint_text=specs['dashboard_public'].hint) }}
|
hint_text=specs['dashboard_public'].hint) }}
|
||||||
{{ checkbox('vnc_enabled', 'Bildschirmzugriff (VNC) anbieten',
|
|
||||||
checked=values.vnc_enabled,
|
|
||||||
hint_text=specs['vnc_enabled'].hint) }}
|
|
||||||
<div class="form-grid">
|
|
||||||
{{ field('vnc_port', 'VNC-Port', value=values.vnc_port, type='number',
|
|
||||||
hint_text=specs['vnc_port'].hint) }}
|
|
||||||
</div>
|
|
||||||
{{ checkbox('vnc_warn_unencrypted_off',
|
|
||||||
'Warnung des Viewers vor unverschlüsselter Verbindung unterdrücken',
|
|
||||||
checked=values.vnc_warn_unencrypted_off,
|
|
||||||
hint_text=specs['vnc_warn_unencrypted_off'].hint) }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card__footer"><button class="btn btn--primary" type="submit">Speichern</button></div>
|
<div class="card__footer"><button class="btn btn--primary" type="submit">Speichern</button></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -117,6 +106,39 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<div class="card__header">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="card__title">{{ icon('monitor', size=17) }} Bildschirm (VNC)</div>
|
||||||
|
<div class="card__sub">
|
||||||
|
Dieselben Regeln wie bei der Konsole: eigenes Recht, frische
|
||||||
|
Passwortbestätigung, getrennte Zwischenablage. Der Knopf erscheint nur, wo
|
||||||
|
ein VNC-Server antwortet – das prüft der Überwachungsdurchlauf mit.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="post" action="{{ url_for('settings.system_save') }}">
|
||||||
|
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
|
||||||
|
<div class="card__body stack">
|
||||||
|
{{ checkbox('vnc_enabled', 'Bildschirmzugriff (VNC) anbieten',
|
||||||
|
checked=values.vnc_enabled,
|
||||||
|
hint_text=specs['vnc_enabled'].hint) }}
|
||||||
|
<div class="form-grid">
|
||||||
|
{{ field('vnc_port', 'VNC-Port', value=values.vnc_port, type='number',
|
||||||
|
hint_text=specs['vnc_port'].hint) }}
|
||||||
|
{{ field('vnc_idle_timeout', 'Trennen bei Untätigkeit (Sekunden)',
|
||||||
|
value=values.vnc_idle_timeout, type='number',
|
||||||
|
hint_text=specs['vnc_idle_timeout'].hint) }}
|
||||||
|
</div>
|
||||||
|
{{ checkbox('vnc_warn_unencrypted_off',
|
||||||
|
'Warnung des Viewers vor unverschlüsselter Verbindung unterdrücken',
|
||||||
|
checked=values.vnc_warn_unencrypted_off,
|
||||||
|
hint_text=specs['vnc_warn_unencrypted_off'].hint) }}
|
||||||
|
</div>
|
||||||
|
<div class="card__footer"><button class="btn btn--primary" type="submit">Speichern</button></div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<div class="card__header">
|
<div class="card__header">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm-core"
|
name = "tesm-core"
|
||||||
version = "2.8.9"
|
version = "2.8.10"
|
||||||
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
|
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -198,7 +198,10 @@ function starten(wurzel) {
|
|||||||
knopfTrennen.hidden = true;
|
knopfTrennen.hidden = true;
|
||||||
knopfStrg.hidden = true;
|
knopfStrg.hidden = true;
|
||||||
if (ereignis.detail && ereignis.detail.clean) {
|
if (ereignis.detail && ereignis.detail.clean) {
|
||||||
melden("Sitzung beendet.");
|
// Beim sauberen Schluss steht der Grund im Schliessrahmen -- etwa,
|
||||||
|
// dass TESM wegen Untätigkeit getrennt hat. Ohne ihn stuende da
|
||||||
|
// "beendet", und niemand wuesste, warum das Bild weg ist.
|
||||||
|
melden(letzterGrund ? `Sitzung beendet – ${letzterGrund}` : "Sitzung beendet.");
|
||||||
} else if (!stand && naechsterVersuch()) {
|
} else if (!stand && naechsterVersuch()) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in new issue
Block a user