Version 2.8.16: die Ueberwachung sperrt sich nicht mehr selbst aus
This commit is contained in:
1 parent
270ba455a8
commit
b54b87ca6f
8 files changed
+196
-42
No files matched your search
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm"
|
name = "tesm"
|
||||||
version = "2.8.15"
|
version = "2.8.16"
|
||||||
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 = [
|
||||||
|
|||||||
@@ -389,10 +389,13 @@ def _vnc_zugang(conn: Any, device: dict[str, Any]) -> vnc.Zugang | None:
|
|||||||
return None
|
return None
|
||||||
port = int(extension.settings.get(conn, "vnc_port") or vnc.STANDARD_PORT)
|
port = int(extension.settings.get(conn, "vnc_port") or vnc.STANDARD_PORT)
|
||||||
online = str(device.get("state") or "") == "online" and not device.get("deleted_at")
|
online = str(device.get("state") or "") == "online" and not device.get("deleted_at")
|
||||||
zugang = vnc.zugang(device, port=port, pruefen=online)
|
zugang, frisch = vnc.zugang_gemerkt(device, port=port, pruefen=online)
|
||||||
if online:
|
if frisch:
|
||||||
with transaction():
|
with transaction():
|
||||||
monitor.merke_vnc(conn, int(device["id"]), zugang.erreichbar, zugang.ziel)
|
monitor.merke_vnc(conn, int(device["id"]), zugang.erreichbar, zugang.ziel)
|
||||||
|
monitor.merke_verfahren(
|
||||||
|
conn, int(device["id"]), zugang.verfahren, zugang.absage
|
||||||
|
)
|
||||||
return zugang
|
return zugang
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,17 @@ def screen(device_id: int):
|
|||||||
if not port:
|
if not port:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
zugang = vnc.zugang(device, port=port, pruefen=str(device.get("state") or "") == "online")
|
zugang, frisch = vnc.zugang_gemerkt(
|
||||||
|
device, port=port, pruefen=str(device.get("state") or "") == "online"
|
||||||
|
)
|
||||||
|
if frisch:
|
||||||
|
from ..services import monitor
|
||||||
|
|
||||||
|
with core().database.transaction(conn):
|
||||||
|
monitor.merke_vnc(conn, int(device["id"]), zugang.erreichbar, zugang.ziel)
|
||||||
|
monitor.merke_verfahren(
|
||||||
|
conn, int(device["id"]), zugang.verfahren, zugang.absage
|
||||||
|
)
|
||||||
benutzer, passwort = "", ""
|
benutzer, passwort = "", ""
|
||||||
darf_geheimnis = bool(current_user.has("credentials.secrets"))
|
darf_geheimnis = bool(current_user.has("credentials.secrets"))
|
||||||
if device.get("credential_id") and darf_geheimnis:
|
if device.get("credential_id") and darf_geheimnis:
|
||||||
|
|||||||
@@ -526,6 +526,19 @@ ALTER TABLE device_status ADD COLUMN vnc_types TEXT NOT NULL DEFAULT '';
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Version 13 ----------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Die Absage eines VNC-Servers und der Zeitpunkt der Verfahrensmessung. Beide
|
||||||
|
# gehoeren getrennt vom Ueberwachungsdurchlauf gefuehrt: der fragt nur noch,
|
||||||
|
# ob der Port annimmt (eine abgebrochene Protokollverhandlung sperrt bei
|
||||||
|
# RealVNC die eigene Adresse aus), die Verfahren misst die Seite -- und auch
|
||||||
|
# die nur, wenn die letzte Messung alt ist.
|
||||||
|
V13 = """
|
||||||
|
ALTER TABLE device_status ADD COLUMN vnc_reason TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE device_status ADD COLUMN vnc_types_at TEXT;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def uebergang_standardgruppen(conn) -> None: # type: ignore[no-untyped-def]
|
def uebergang_standardgruppen(conn) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Aus sieben Standardgruppen werden drei -- ohne jemandem etwas wegzunehmen.
|
"""Aus sieben Standardgruppen werden drei -- ohne jemandem etwas wegzunehmen.
|
||||||
|
|
||||||
@@ -571,4 +584,5 @@ TESM_MIGRATIONS: tuple[Migration, ...] = (
|
|||||||
Migration(version=10, name="vnc-erreichbarkeit", sql=V10),
|
Migration(version=10, name="vnc-erreichbarkeit", sql=V10),
|
||||||
Migration(version=11, name="vnc-ziel", sql=V11),
|
Migration(version=11, name="vnc-ziel", sql=V11),
|
||||||
Migration(version=12, name="vnc-verfahren", sql=V12),
|
Migration(version=12, name="vnc-verfahren", sql=V12),
|
||||||
|
Migration(version=13, name="vnc-absage", sql=V13),
|
||||||
)
|
)
|
||||||
@@ -544,7 +544,8 @@ def list_devices(
|
|||||||
st.last_ok_at, st.last_change_at, st.last_restart_at, st.restart_count,
|
st.last_ok_at, st.last_change_at, st.last_restart_at, st.restart_count,
|
||||||
st.fail_count, COALESCE(st.vnc_open, 0) AS vnc_open, st.vnc_checked_at,
|
st.fail_count, COALESCE(st.vnc_open, 0) AS vnc_open, st.vnc_checked_at,
|
||||||
COALESCE(st.vnc_target, '') AS vnc_target,
|
COALESCE(st.vnc_target, '') AS vnc_target,
|
||||||
COALESCE(st.vnc_types, '') AS vnc_types
|
COALESCE(st.vnc_types, '') AS vnc_types,
|
||||||
|
COALESCE(st.vnc_reason, '') AS vnc_reason, st.vnc_types_at
|
||||||
FROM devices d
|
FROM devices d
|
||||||
LEFT JOIN switches s ON s.id = d.switch_id AND s.deleted_at IS NULL
|
LEFT JOIN switches s ON s.id = d.switch_id AND s.deleted_at IS NULL
|
||||||
LEFT JOIN credentials c ON c.id = d.credential_id AND c.deleted_at IS NULL
|
LEFT JOIN credentials c ON c.id = d.credential_id AND c.deleted_at IS NULL
|
||||||
|
|||||||
@@ -507,30 +507,36 @@ class SweepReport:
|
|||||||
duration_ms: int = 0
|
duration_ms: int = 0
|
||||||
|
|
||||||
|
|
||||||
def merke_vnc(
|
def merke_vnc(conn: sqlite3.Connection, device_id: int, offen: bool, ziel: str = "") -> None:
|
||||||
conn: sqlite3.Connection,
|
|
||||||
device_id: int,
|
|
||||||
offen: bool,
|
|
||||||
ziel: str = "",
|
|
||||||
verfahren: tuple[int, ...] = (),
|
|
||||||
) -> None:
|
|
||||||
"""Haelt fest, ob dort ein VNC-Server antwortet -- und unter welchem Ziel.
|
"""Haelt fest, ob dort ein VNC-Server antwortet -- und unter welchem Ziel.
|
||||||
|
|
||||||
Das Ziel gehoert dazu, weil die Statusuebersicht die Verbindung unmittelbar
|
Das Ziel gehoert dazu, weil die Statusuebersicht die Verbindung unmittelbar
|
||||||
verlinkt. Es aus dem Namen neu herzuleiten hiesse, je Kachel und
|
verlinkt. Es aus dem Namen neu herzuleiten hiesse, je Kachel und
|
||||||
Seitenaufbau eine Namensaufloesung zu versuchen; hier steht es einmal
|
Seitenaufbau eine Namensaufloesung zu versuchen; hier steht es einmal
|
||||||
geschrieben, von dem, der ohnehin gefragt hat.
|
geschrieben, von dem, der ohnehin gefragt hat.
|
||||||
|
|
||||||
|
Die Anmeldeverfahren bleiben unberuehrt: sie kommen aus einer anderen
|
||||||
|
Quelle (:func:`merke_verfahren`) und haetten hier nur den Wert "nicht
|
||||||
|
gefragt" -- der wuerde eine gute Messung ueberschreiben.
|
||||||
"""
|
"""
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE device_status SET vnc_open=?, vnc_checked_at=?, vnc_target=?, vnc_types=? "
|
"UPDATE device_status SET vnc_open=?, vnc_checked_at=?, vnc_target=? WHERE device_id=?",
|
||||||
"WHERE device_id=?",
|
(1 if offen else 0, _now(), ziel, device_id),
|
||||||
(
|
)
|
||||||
1 if offen else 0,
|
|
||||||
_now(),
|
|
||||||
ziel,
|
def merke_verfahren(
|
||||||
",".join(str(v) for v in verfahren),
|
conn: sqlite3.Connection, device_id: int, verfahren: tuple[int, ...], absage: str = ""
|
||||||
device_id,
|
) -> None:
|
||||||
),
|
"""Haelt fest, wie sich der VNC-Server authentifizieren will.
|
||||||
|
|
||||||
|
Mit Zeitstempel, denn diese Messung kostet eine angefangene
|
||||||
|
Protokollverhandlung -- und die soll sich nicht bei jedem Seitenaufbau
|
||||||
|
wiederholen.
|
||||||
|
"""
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE device_status SET vnc_types=?, vnc_reason=?, vnc_types_at=? WHERE device_id=?",
|
||||||
|
(",".join(str(v) for v in verfahren), absage, _now(), device_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -553,6 +559,10 @@ def run_sweep(
|
|||||||
erreichbare Geraet: nimmt dort ein VNC-Server Verbindungen an? Sie gehoert
|
erreichbare Geraet: nimmt dort ein VNC-Server Verbindungen an? Sie gehoert
|
||||||
hierher und nicht in den Seitenaufbau -- einmal je Intervall statt einmal
|
hierher und nicht in den Seitenaufbau -- einmal je Intervall statt einmal
|
||||||
je Aufruf, und nur fuer Geraete, die ohnehin gerade antworten.
|
je Aufruf, und nur fuer Geraete, die ohnehin gerade antworten.
|
||||||
|
|
||||||
|
**Nur die Frage nach dem Port**, kein Wort Protokoll: ein RealVNC Server
|
||||||
|
zaehlt abgebrochene Verhandlungen und sperrt die Adresse eine Weile aus.
|
||||||
|
Welche Anmeldeverfahren er anbietet, misst die Seite, die es braucht.
|
||||||
"""
|
"""
|
||||||
from . import vnc as vnc_dienst # spaet: vnc benutzt tcp_reachable von hier
|
from . import vnc as vnc_dienst # spaet: vnc benutzt tcp_reachable von hier
|
||||||
|
|
||||||
@@ -575,7 +585,7 @@ def run_sweep(
|
|||||||
# bekannten Stand stehen. Ein Geraet, das gerade neu startet, soll
|
# bekannten Stand stehen. Ein Geraet, das gerade neu startet, soll
|
||||||
# seinen Knopf nicht verlieren.
|
# seinen Knopf nicht verlieren.
|
||||||
return device, ergebnis, None
|
return device, ergebnis, None
|
||||||
return device, ergebnis, vnc_dienst.zugang(device, port=vnc_port)
|
return device, ergebnis, vnc_dienst.zugang_kurz(device, port=vnc_port)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=min(MAX_PARALLEL_CHECKS, len(devices))) as pool:
|
with ThreadPoolExecutor(max_workers=min(MAX_PARALLEL_CHECKS, len(devices))) as pool:
|
||||||
results = list(pool.map(pruefen, devices))
|
results = list(pool.map(pruefen, devices))
|
||||||
@@ -587,13 +597,7 @@ def run_sweep(
|
|||||||
report.checked += 1
|
report.checked += 1
|
||||||
previous, current = record_status(conn, int(device["id"]), result)
|
previous, current = record_status(conn, int(device["id"]), result)
|
||||||
if vnc_offen is not None:
|
if vnc_offen is not None:
|
||||||
merke_vnc(
|
merke_vnc(conn, int(device["id"]), vnc_offen.erreichbar, vnc_offen.ziel)
|
||||||
conn,
|
|
||||||
int(device["id"]),
|
|
||||||
vnc_offen.erreichbar,
|
|
||||||
vnc_offen.ziel,
|
|
||||||
vnc_offen.verfahren,
|
|
||||||
)
|
|
||||||
if result.ok:
|
if result.ok:
|
||||||
report.online += 1
|
report.online += 1
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ class Zugang:
|
|||||||
#: gemessen). Daraus entsteht der Satz, warum eine Sitzung im Browser
|
#: gemessen). Daraus entsteht der Satz, warum eine Sitzung im Browser
|
||||||
#: gehen wird oder nicht.
|
#: gehen wird oder nicht.
|
||||||
verfahren: tuple[int, ...] = ()
|
verfahren: tuple[int, ...] = ()
|
||||||
|
#: Der Text, mit dem der Server die Verbindung abgelehnt hat -- er nennt
|
||||||
|
#: dann kein Verfahren, sondern eine 0 und einen Grund.
|
||||||
|
absage: str = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def browsertauglich(self) -> bool:
|
def browsertauglich(self) -> bool:
|
||||||
@@ -72,6 +75,14 @@ class Zugang:
|
|||||||
"""Warum eine Sitzung im Browser nicht zustande kaeme -- im Klartext."""
|
"""Warum eine Sitzung im Browser nicht zustande kaeme -- im Klartext."""
|
||||||
if not self.erreichbar:
|
if not self.erreichbar:
|
||||||
return f"Auf {self.ziel}:{self.port} antwortet kein VNC-Server."
|
return f"Auf {self.ziel}:{self.port} antwortet kein VNC-Server."
|
||||||
|
if self.absage:
|
||||||
|
return (
|
||||||
|
f"Der VNC-Server auf {self.ziel} weist die Verbindung ab: "
|
||||||
|
f"„{self.absage}“. Das ist seine Auskunft, nicht unsere – "
|
||||||
|
"typisch sind eine Sperre nach zu vielen Fehlversuchen "
|
||||||
|
"(RealVNC: BlacklistTimeout), eine Adressfilterung oder eine "
|
||||||
|
"belegte Sitzung."
|
||||||
|
)
|
||||||
if not self.verfahren:
|
if not self.verfahren:
|
||||||
return ""
|
return ""
|
||||||
if self.browsertauglich:
|
if self.browsertauglich:
|
||||||
@@ -181,7 +192,36 @@ def verfahren_name(nummer: int) -> str:
|
|||||||
return str(nummer)
|
return str(nummer)
|
||||||
|
|
||||||
|
|
||||||
def sicherheitsverfahren(host: str, port: int = STANDARD_PORT) -> tuple[str, tuple[int, ...]]:
|
def _absage(sock: Any) -> str:
|
||||||
|
"""Der Grund, den ein Server einer abgelehnten Verbindung mitgibt.
|
||||||
|
|
||||||
|
Auf eine 0 -- ob als Anzahl (ab 3.7) oder als bestimmtes Verfahren (3.3) --
|
||||||
|
folgt eine Laenge und ein Text. Genau dieser Satz ist die Auskunft, die
|
||||||
|
sonst niemand bekommt: "zu viele Fehlversuche", "Verbindung von dieser
|
||||||
|
Adresse nicht erlaubt", "kein gemeinsames Verfahren". Ohne ihn steht da
|
||||||
|
nur, dass es nicht geht.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
roh = sock.recv(4)
|
||||||
|
if len(roh) < 4:
|
||||||
|
return ""
|
||||||
|
laenge = int.from_bytes(roh, "big")
|
||||||
|
if not 0 < laenge <= 1024:
|
||||||
|
return ""
|
||||||
|
text = b""
|
||||||
|
while len(text) < laenge:
|
||||||
|
stueck = sock.recv(laenge - len(text))
|
||||||
|
if not stueck:
|
||||||
|
break
|
||||||
|
text += stueck
|
||||||
|
return text.decode("utf-8", "replace").strip()
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def sicherheitsverfahren(
|
||||||
|
host: str, port: int = STANDARD_PORT
|
||||||
|
) -> tuple[str, tuple[int, ...], str]:
|
||||||
"""Fragt den Server, **wie** er sich authentifizieren will.
|
"""Fragt den Server, **wie** er sich authentifizieren will.
|
||||||
|
|
||||||
Der Anfang einer VNC-Verbindung ist fuer jeden Server derselbe: er nennt
|
Der Anfang einer VNC-Verbindung ist fuer jeden Server derselbe: er nennt
|
||||||
@@ -192,8 +232,12 @@ def sicherheitsverfahren(host: str, port: int = STANDARD_PORT) -> tuple[str, tup
|
|||||||
Das ist die Angabe, an der eine Bildschirmsitzung im Browser scheitert oder
|
Das ist die Angabe, an der eine Bildschirmsitzung im Browser scheitert oder
|
||||||
gelingt, und sie hier zu messen ist der Unterschied zwischen einer
|
gelingt, und sie hier zu messen ist der Unterschied zwischen einer
|
||||||
schwarzen Flaeche und dem Satz "dieser Server bietet nur Verfahren an, die
|
schwarzen Flaeche und dem Satz "dieser Server bietet nur Verfahren an, die
|
||||||
der Browser nicht kennt". Gibt ``(Fassung, Verfahren)`` zurueck; bei einem
|
der Browser nicht kennt".
|
||||||
Fehler ``("", ())``.
|
|
||||||
|
Gibt ``(Fassung, Verfahren, Absage)`` zurueck. Die Absage ist der Text, den
|
||||||
|
ein Server mitschickt, wenn er die Verbindung **ablehnt** -- er meldet dann
|
||||||
|
kein Verfahren, sondern eine 0 und einen Grund. Bei einem Fehler
|
||||||
|
``("", (), "")``.
|
||||||
"""
|
"""
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
@@ -202,7 +246,7 @@ def sicherheitsverfahren(host: str, port: int = STANDARD_PORT) -> tuple[str, tup
|
|||||||
sock.settimeout(ZEITLIMIT)
|
sock.settimeout(ZEITLIMIT)
|
||||||
gruss = sock.recv(12)
|
gruss = sock.recv(12)
|
||||||
if not gruss.startswith(b"RFB "):
|
if not gruss.startswith(b"RFB "):
|
||||||
return "", ()
|
return "", (), ""
|
||||||
fassung = gruss[4:11].decode("ascii", "replace")
|
fassung = gruss[4:11].decode("ascii", "replace")
|
||||||
# Wir antworten mit 3.8 -- der Fassung, die auch der Browser
|
# Wir antworten mit 3.8 -- der Fassung, die auch der Browser
|
||||||
# spricht. Ein Server, der mehr kann, faellt darauf zurueck und
|
# spricht. Ein Server, der mehr kann, faellt darauf zurueck und
|
||||||
@@ -225,26 +269,45 @@ def sicherheitsverfahren(host: str, port: int = STANDARD_PORT) -> tuple[str, tup
|
|||||||
break
|
break
|
||||||
rohwert += stueck
|
rohwert += stueck
|
||||||
if len(rohwert) < 4:
|
if len(rohwert) < 4:
|
||||||
return fassung, ()
|
return fassung, (), ""
|
||||||
bestimmt = int.from_bytes(rohwert, "big")
|
bestimmt = int.from_bytes(rohwert, "big")
|
||||||
return fassung, () if bestimmt == 0 else (bestimmt,)
|
if bestimmt == 0:
|
||||||
|
return fassung, (), _absage(sock)
|
||||||
|
return fassung, (bestimmt,), ""
|
||||||
|
|
||||||
anzahl = sock.recv(1)
|
anzahl = sock.recv(1)
|
||||||
if not anzahl:
|
if not anzahl:
|
||||||
return fassung, ()
|
return fassung, (), ""
|
||||||
wie_viele = anzahl[0]
|
wie_viele = anzahl[0]
|
||||||
if wie_viele == 0:
|
if wie_viele == 0:
|
||||||
# Null heisst: der Server lehnt ab und schickt den Grund.
|
# Null heisst: der Server lehnt ab und schickt den Grund.
|
||||||
return fassung, ()
|
return fassung, (), _absage(sock)
|
||||||
liste = b""
|
liste = b""
|
||||||
while len(liste) < wie_viele:
|
while len(liste) < wie_viele:
|
||||||
stueck = sock.recv(wie_viele - len(liste))
|
stueck = sock.recv(wie_viele - len(liste))
|
||||||
if not stueck:
|
if not stueck:
|
||||||
break
|
break
|
||||||
liste += stueck
|
liste += stueck
|
||||||
return fassung, tuple(liste)
|
return fassung, tuple(liste), ""
|
||||||
except OSError:
|
except OSError:
|
||||||
return "", ()
|
return "", (), ""
|
||||||
|
|
||||||
|
|
||||||
|
def zugang_kurz(device: Mapping[str, Any], *, port: int = STANDARD_PORT) -> Zugang:
|
||||||
|
"""Nur die Frage, ob dort jemand annimmt -- ohne ein Wort Protokoll.
|
||||||
|
|
||||||
|
Das ist die Fassung fuer den Ueberwachungsdurchlauf. Er laeuft alle paar
|
||||||
|
Minuten ueber jedes Geraet, und eine angefangene und sofort wieder
|
||||||
|
abgebrochene Protokollverhandlung ist nicht nur unhoeflich: ein RealVNC
|
||||||
|
Server zaehlt abgebrochene Versuche und sperrt die Adresse eine Weile aus
|
||||||
|
(``BlacklistTimeout``). Eine Ueberwachung, die ihr eigenes Ziel aussperrt,
|
||||||
|
waere ein schoener Eigentor -- also fragt sie nur, ob der Port annimmt.
|
||||||
|
|
||||||
|
Welche Anmeldeverfahren der Server kann, misst erst die Seite, die es
|
||||||
|
wirklich braucht -- und auch die nicht oefter als noetig.
|
||||||
|
"""
|
||||||
|
host = ziel(device)
|
||||||
|
return Zugang(ziel=host, port=port, erreichbar=erreichbar(host, port))
|
||||||
|
|
||||||
|
|
||||||
def zugang(device: Mapping[str, Any], *, port: int = STANDARD_PORT, pruefen: bool = True) -> Zugang:
|
def zugang(device: Mapping[str, Any], *, port: int = STANDARD_PORT, pruefen: bool = True) -> Zugang:
|
||||||
@@ -252,8 +315,66 @@ def zugang(device: Mapping[str, Any], *, port: int = STANDARD_PORT, pruefen: boo
|
|||||||
host = ziel(device)
|
host = ziel(device)
|
||||||
if not pruefen:
|
if not pruefen:
|
||||||
return Zugang(ziel=host, port=port, erreichbar=False)
|
return Zugang(ziel=host, port=port, erreichbar=False)
|
||||||
fassung, verfahren = sicherheitsverfahren(host, port)
|
fassung, verfahren, absage = sicherheitsverfahren(host, port)
|
||||||
return Zugang(ziel=host, port=port, erreichbar=bool(fassung), verfahren=verfahren)
|
return Zugang(
|
||||||
|
ziel=host,
|
||||||
|
port=port,
|
||||||
|
erreichbar=bool(fassung),
|
||||||
|
verfahren=verfahren,
|
||||||
|
absage=absage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#: So lange gilt eine Messung der Anmeldeverfahren als frisch.
|
||||||
|
#:
|
||||||
|
#: Sie kostet eine angefangene Protokollverhandlung, und ein RealVNC Server
|
||||||
|
#: zaehlt abgebrochene Versuche. Wer eine Seite dreimal neu laedt, soll dem
|
||||||
|
#: Geraet nicht dreimal als Fehlversuch erscheinen.
|
||||||
|
MESSUNG_GILT = 300.0
|
||||||
|
|
||||||
|
|
||||||
|
def zugang_gemerkt(
|
||||||
|
device: Mapping[str, Any], *, port: int = STANDARD_PORT, pruefen: bool = True
|
||||||
|
) -> tuple[Zugang, bool]:
|
||||||
|
"""Der Zugang -- aus der letzten Messung, wenn die frisch genug ist.
|
||||||
|
|
||||||
|
Gibt ``(Zugang, frisch_gemessen)`` zurueck; das zweite sagt dem Aufrufer,
|
||||||
|
ob er das Ergebnis festhalten muss.
|
||||||
|
"""
|
||||||
|
host = ziel(device)
|
||||||
|
if not pruefen:
|
||||||
|
return Zugang(ziel=host, port=port, erreichbar=False), False
|
||||||
|
|
||||||
|
gemessen_am = str(device.get("vnc_types_at") or "")
|
||||||
|
if gemessen_am and _juenger_als(gemessen_am, MESSUNG_GILT):
|
||||||
|
gespeichert = str(device.get("vnc_types") or "")
|
||||||
|
verfahren = tuple(
|
||||||
|
int(teil) for teil in gespeichert.split(",") if teil.strip().isdigit()
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
Zugang(
|
||||||
|
ziel=host,
|
||||||
|
port=port,
|
||||||
|
erreichbar=bool(device.get("vnc_open")),
|
||||||
|
verfahren=verfahren,
|
||||||
|
absage=str(device.get("vnc_reason") or ""),
|
||||||
|
),
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
return zugang(device, port=port), True
|
||||||
|
|
||||||
|
|
||||||
|
def _juenger_als(zeitpunkt: str, sekunden: float) -> bool:
|
||||||
|
"""Ist dieser ISO-Zeitstempel juenger als die Frist?"""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
try:
|
||||||
|
moment = datetime.fromisoformat(zeitpunkt)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
if moment.tzinfo is None:
|
||||||
|
moment = moment.replace(tzinfo=timezone.utc)
|
||||||
|
return (datetime.now(timezone.utc) - moment).total_seconds() < sekunden
|
||||||
|
|
||||||
|
|
||||||
def verbindungsdatei(
|
def verbindungsdatei(
|
||||||
|
|||||||
@@ -30,7 +30,8 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="card__title">
|
<div class="card__title">
|
||||||
{{ icon('monitor', size=17) }} {{ device.name }}
|
{{ icon('monitor', size=17) }} {{ device.name }}
|
||||||
{% if zugang.verfahren %}
|
{% if zugang.absage %}{{ pill('Verbindung abgelehnt', 'warning') }}
|
||||||
|
{% elif zugang.verfahren %}
|
||||||
{% if zugang.browsertauglich %}{{ pill('Browser-Sitzung möglich', 'online') }}
|
{% if zugang.browsertauglich %}{{ pill('Browser-Sitzung möglich', 'online') }}
|
||||||
{% else %}{{ pill('Verfahren nicht unterstützt', 'warning') }}{% endif %}
|
{% else %}{{ pill('Verfahren nicht unterstützt', 'warning') }}{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in new issue
Block a user