Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a528e9f3b2 | ||
|
|
3b3ce96678 | ||
|
|
42316ba137 |
No files matched your search
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm"
|
name = "tesm"
|
||||||
version = "2.8.18"
|
version = "2.8.21"
|
||||||
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 = [
|
||||||
|
|||||||
@@ -411,12 +411,14 @@ def vnc_connection(device_id: int):
|
|||||||
ziel = vnc.ziel(device)
|
ziel = vnc.ziel(device)
|
||||||
if not ziel:
|
if not ziel:
|
||||||
abort(404)
|
abort(404)
|
||||||
with transaction():
|
# Kein Eintrag in der Aenderungskette: eine Verbindungsdatei ist derselbe
|
||||||
_audit(
|
# Vorgang wie die Sitzung im Browser, nur fuer einen Viewer auf dem
|
||||||
conn,
|
# Arbeitsplatz -- und sie aendert nichts. Wer zusieht, steht im laufenden
|
||||||
"device.vnc_opened",
|
# Protokoll; das Aenderungsprotokoll bleibt die Liste der Eingriffe.
|
||||||
str(device["name"]),
|
live = core().registry.get("live_log")
|
||||||
f"Verbindungsdatei für {ziel}:{port}",
|
if live is not None:
|
||||||
|
live.info(
|
||||||
|
"vnc", f"{current_user.username} lädt die Verbindungsdatei für {device['name']}"
|
||||||
)
|
)
|
||||||
return Response(
|
return Response(
|
||||||
vnc.verbindungsdatei(
|
vnc.verbindungsdatei(
|
||||||
|
|||||||
@@ -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,37 +235,91 @@ 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* -- als Abbruchgrund taugt es nicht.
|
||||||
|
#
|
||||||
|
# ``create_connection`` setzt es an den Socket, und dort gilt es fuer jede
|
||||||
|
# weitere Leseoperation. Ein Bildschirm, auf dem sich gerade nichts tut,
|
||||||
|
# schickt sekundenlang nichts; nach sechs Sekunden Stille brach das Lesen
|
||||||
|
# mit einer Zeitueberschreitung ab, und die Sitzung war weg. Genau so
|
||||||
|
# beobachtet: Bild steht, nach fuenf bis zehn Sekunden Schluss.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# Bildschirmsitzung aendert nichts an der Anlage. Das Aenderungsprotokoll
|
||||||
|
# soll beantworten, wer was umgestellt hat -- wer wann zugesehen hat,
|
||||||
|
# gehoert nicht in dieselbe Liste und macht sie in einem Betrieb mit
|
||||||
|
# ein paar Dutzend Geraeten unlesbar. Sichtbar ist die Sitzung trotzdem,
|
||||||
|
# solange sie laeuft.
|
||||||
begonnen = time.monotonic()
|
begonnen = time.monotonic()
|
||||||
with extension.database.transaction(conn):
|
live = extension.registry.get("live_log")
|
||||||
audit.log(
|
if live is not None:
|
||||||
conn,
|
live.info("vnc", f"{current_user.username} öffnet den Bildschirm von {device['name']}")
|
||||||
actor=current_user.username,
|
|
||||||
actor_id=current_user.id,
|
|
||||||
action="device.screen_opened",
|
|
||||||
target=str(device["name"]),
|
|
||||||
detail=f"Bildschirmsitzung zu {host}:{port}",
|
|
||||||
ip=client_ip(),
|
|
||||||
severity="warning",
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -278,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)
|
||||||
@@ -289,14 +348,9 @@ def _strecke(ws, device_id: int) -> None: # type: ignore[no-untyped-def]
|
|||||||
pass
|
pass
|
||||||
gegenstelle.close()
|
gegenstelle.close()
|
||||||
dauer = int(time.monotonic() - begonnen)
|
dauer = int(time.monotonic() - begonnen)
|
||||||
with extension.database.session() as frisch, extension.database.transaction(frisch):
|
if live is not None:
|
||||||
audit.log(
|
live.info(
|
||||||
frisch,
|
"vnc",
|
||||||
actor=current_user.username,
|
f"Bildschirmsitzung {device['name']} beendet ({dauer}s)"
|
||||||
actor_id=current_user.id,
|
+ (f" – {schluss[0]}" if schluss[0] else ""),
|
||||||
action="device.screen_closed",
|
|
||||||
target=str(device["name"]),
|
|
||||||
detail=f"Dauer {dauer}s",
|
|
||||||
ip=client_ip(),
|
|
||||||
severity="info",
|
|
||||||
)
|
)
|
||||||
@@ -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.11"
|
||||||
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 = [
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ const gescheitert = new Set();
|
|||||||
//: RealVNC Server zaehlt abgebrochene Versuche mit -- wer dreimal anklopft,
|
//: RealVNC Server zaehlt abgebrochene Versuche mit -- wer dreimal anklopft,
|
||||||
//: sperrt sich aus.
|
//: sperrt sich aus.
|
||||||
const HOECHSTENS = 2;
|
const HOECHSTENS = 2;
|
||||||
|
|
||||||
|
//: So lange darf "wird aufgebaut" stehen, bevor die Seite selbst nachsieht.
|
||||||
|
//:
|
||||||
|
//: Der Server hat eigene Grenzen und meldet sich, wenn das Geraet schweigt --
|
||||||
|
//: aber nur, wenn die Leitung dorthin ueberhaupt steht. Kommt die
|
||||||
|
//: WebSocket-Verbindung gar nicht erst zustande, wartet noVNC ohne jedes
|
||||||
|
//: Ereignis, und die Seite stuende stumm bis zum Seitenwechsel. Genau so
|
||||||
|
//: gemeldet: "Verbindung wird aufgebaut ..." und sonst nichts.
|
||||||
|
const AUFBAU_GRENZE = 12000;
|
||||||
// Den *Grund* eines Fehlschlags kennt nur ``_fail`` -- das Ereignis
|
// Den *Grund* eines Fehlschlags kennt nur ``_fail`` -- das Ereignis
|
||||||
// "disconnect" traegt ihn nicht, und der Weg ueber die Protokollausgabe
|
// "disconnect" traegt ihn nicht, und der Weg ueber die Protokollausgabe
|
||||||
// funktioniert nicht: die Bibliothek bindet ``console.error`` beim Laden, also
|
// funktioniert nicht: die Bibliothek bindet ``console.error`` beim Laden, also
|
||||||
@@ -87,6 +96,7 @@ function starten(wurzel) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let rfb = null;
|
let rfb = null;
|
||||||
|
let aufbauUhr = 0;
|
||||||
let versuche = 0;
|
let versuche = 0;
|
||||||
let stand = false;
|
let stand = false;
|
||||||
let letzterZugang = {};
|
let letzterZugang = {};
|
||||||
@@ -164,6 +174,25 @@ function starten(wurzel) {
|
|||||||
letzterCode = 0;
|
letzterCode = 0;
|
||||||
letzterZugang = zugang;
|
letzterZugang = zugang;
|
||||||
versuche += 1;
|
versuche += 1;
|
||||||
|
window.clearTimeout(aufbauUhr);
|
||||||
|
aufbauUhr = window.setTimeout(() => {
|
||||||
|
// Weder verbunden noch abgebrochen noch nach Zugangsdaten gefragt --
|
||||||
|
// dann haengt es an der Leitung, nicht am VNC-Server.
|
||||||
|
melden("Es kommt keine Antwort. Wird geprüft, woran es liegt …", "warnung");
|
||||||
|
streckeTesten().then((steht) => {
|
||||||
|
melden(
|
||||||
|
steht
|
||||||
|
? "Die WebSocket-Strecke zu TESM steht, aber die Sitzung kommt nicht " +
|
||||||
|
"zustande. Antwortet der VNC-Server auf dem Gerät noch? Ein Neustart " +
|
||||||
|
"des VNC-Dienstes räumt eine Sperre ab."
|
||||||
|
: "Die WebSocket-Verbindung zu TESM kommt nicht zustande – auch eine " +
|
||||||
|
"einfache Probe scheitert. Ein Proxy zwischen Browser und TESM muss " +
|
||||||
|
"die Kopfzeilen Upgrade und Connection durchreichen (nginx: " +
|
||||||
|
"location /ws/ mit proxy_set_header Upgrade $http_upgrade).",
|
||||||
|
"fehler",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, AUFBAU_GRENZE);
|
||||||
|
|
||||||
// Was abgerissen ist, wird nicht noch einmal gewaehlt. Sonst faende noVNC
|
// Was abgerissen ist, wird nicht noch einmal gewaehlt. Sonst faende noVNC
|
||||||
// dasselbe Verfahren wieder zuerst -- und der zweite Anlauf waere eine
|
// dasselbe Verfahren wieder zuerst -- und der zweite Anlauf waere eine
|
||||||
@@ -185,6 +214,7 @@ function starten(wurzel) {
|
|||||||
rfb.focusOnClick = true;
|
rfb.focusOnClick = true;
|
||||||
|
|
||||||
rfb.addEventListener("connect", () => {
|
rfb.addEventListener("connect", () => {
|
||||||
|
window.clearTimeout(aufbauUhr);
|
||||||
stand = true;
|
stand = true;
|
||||||
melden(`Verbunden mit ${wurzel.dataset.ziel}`, "ok");
|
melden(`Verbunden mit ${wurzel.dataset.ziel}`, "ok");
|
||||||
abfrage.hidden = true;
|
abfrage.hidden = true;
|
||||||
@@ -194,11 +224,15 @@ function starten(wurzel) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rfb.addEventListener("disconnect", (ereignis) => {
|
rfb.addEventListener("disconnect", (ereignis) => {
|
||||||
|
window.clearTimeout(aufbauUhr);
|
||||||
leinwand.hidden = true;
|
leinwand.hidden = true;
|
||||||
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 {
|
||||||
@@ -233,6 +267,7 @@ function starten(wurzel) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rfb.addEventListener("credentialsrequired", (ereignis) => {
|
rfb.addEventListener("credentialsrequired", (ereignis) => {
|
||||||
|
window.clearTimeout(aufbauUhr);
|
||||||
const gefragt = ereignis.detail.types || ["password"];
|
const gefragt = ereignis.detail.types || ["password"];
|
||||||
abfrage.hidden = false;
|
abfrage.hidden = false;
|
||||||
feldBenutzer.closest("label").hidden = !gefragt.includes("username");
|
feldBenutzer.closest("label").hidden = !gefragt.includes("username");
|
||||||
@@ -241,6 +276,7 @@ function starten(wurzel) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rfb.addEventListener("securityfailure", (ereignis) => {
|
rfb.addEventListener("securityfailure", (ereignis) => {
|
||||||
|
window.clearTimeout(aufbauUhr);
|
||||||
melden(
|
melden(
|
||||||
`Anmeldung abgelehnt: ${ereignis.detail.reason || "kein Grund genannt"}`,
|
`Anmeldung abgelehnt: ${ereignis.detail.reason || "kein Grund genannt"}`,
|
||||||
"fehler",
|
"fehler",
|
||||||
|
|||||||
Reference in new issue
Block a user