Version 2.8.48: belegter Code sichtbar, Geraetewerte zaehlen mit
This commit is contained in:
1 parent
5cc34f2a1f
commit
a6d9a46d8f
4 files changed
+109
-24
No files matched your search
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tesm"
|
||||
version = "2.8.47"
|
||||
version = "2.8.48"
|
||||
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
@@ -81,15 +81,61 @@ def overview():
|
||||
for zeile in Database.all(conn, "SELECT mac FROM devices WHERE deleted_at IS NULL")
|
||||
}
|
||||
|
||||
return render_template(
|
||||
"tesm/dhcp.html",
|
||||
subnets=Database.all(conn, "SELECT * FROM dhcp_subnets ORDER BY sort_order, id"),
|
||||
options = Database.all(
|
||||
conn,
|
||||
"SELECT o.*, v.value, v.id AS value_id FROM dhcp_options o "
|
||||
"LEFT JOIN dhcp_option_values v ON v.option_id = o.id AND v.device_id IS NULL "
|
||||
"ORDER BY o.code",
|
||||
),
|
||||
)
|
||||
# Ein Wert an einem einzelnen Geraet ist ein gesendeter Wert. Die Uebersicht
|
||||
# zaehlte nur die globalen -- und meldete "Nichts gesetzt", waehrend die
|
||||
# Vorschau daneben die Option in der Reservierung auswies.
|
||||
je_geraet: dict[int, list[dict[str, Any]]] = {}
|
||||
for zeile in Database.all(
|
||||
conn,
|
||||
"SELECT o.code, d.name AS geraet, v.value FROM dhcp_option_values v "
|
||||
"JOIN dhcp_options o ON o.id = v.option_id "
|
||||
"JOIN devices d ON d.id = v.device_id "
|
||||
"WHERE v.device_id IS NOT NULL AND d.deleted_at IS NULL "
|
||||
"AND TRIM(COALESCE(v.value, '')) <> '' ORDER BY o.code, d.name",
|
||||
):
|
||||
je_geraet.setdefault(int(zeile["code"]), []).append(dict(zeile))
|
||||
|
||||
gesendet = []
|
||||
for option in options:
|
||||
geraete = je_geraet.get(int(option["code"]), [])
|
||||
if str(option["value"] or "").strip() or geraete:
|
||||
eintrag = dict(option)
|
||||
eintrag["geraete"] = geraete
|
||||
gesendet.append(eintrag)
|
||||
|
||||
# Der Server belegt einen Code selbst. Ihn aus dem Katalog herauszuhalten
|
||||
# hiess: er steht in der erzeugten Datei, aber nirgends auf der Seite --
|
||||
# und wer 224 vergeben will, erfaehrt es erst hinterher.
|
||||
eigenes_verzeichnis = kea.verzeichnis(conn)
|
||||
katalog = list(options)
|
||||
if not any(int(zeile["code"]) == kea.KATALOG_CODE for zeile in katalog):
|
||||
katalog.append(
|
||||
{
|
||||
"id": None,
|
||||
"code": kea.KATALOG_CODE,
|
||||
"name": kea.KATALOG_NAME,
|
||||
"type": "string",
|
||||
"description": "Vom Server belegt | nennt den Clients Name und Typ der "
|
||||
"eigenen Optionen",
|
||||
"value": eigenes_verzeichnis,
|
||||
"is_standard": 1,
|
||||
"reserviert": True,
|
||||
}
|
||||
)
|
||||
katalog.sort(key=lambda zeile: int(zeile["code"]))
|
||||
|
||||
return render_template(
|
||||
"tesm/dhcp.html",
|
||||
subnets=Database.all(conn, "SELECT * FROM dhcp_subnets ORDER BY sort_order, id"),
|
||||
options=options,
|
||||
katalog=katalog,
|
||||
gesendet=gesendet,
|
||||
option_types=kea.OPTION_TYPES,
|
||||
device_option_values=_device_option_values(conn),
|
||||
reservations=reservations,
|
||||
@@ -375,6 +421,17 @@ def add_custom_option():
|
||||
if not 1 <= code <= 254:
|
||||
flash("Der Optionscode muss zwischen 1 und 254 liegen.", "danger")
|
||||
return redirect(url_for("dhcp.overview"))
|
||||
if code == kea.KATALOG_CODE:
|
||||
# Hier abweisen statt spaeter stillschweigend das Verzeichnis
|
||||
# wegzulassen: sonst funktionierte die Benennung auf den Clients
|
||||
# nicht mehr, und niemand wuesste warum.
|
||||
flash(
|
||||
f"Code {kea.KATALOG_CODE} ist belegt: darunter sendet der Server seinen Clients "
|
||||
"die Namen und Typen der eigenen Optionen. Ohne dieses Verzeichnis heißt eine "
|
||||
"neue Option auf dem Client nur noch nach ihrer Nummer.",
|
||||
"danger",
|
||||
)
|
||||
return redirect(url_for("dhcp.overview"))
|
||||
if not name or option_type not in kea.OPTION_TYPES:
|
||||
flash("Name und Typ sind erforderlich.", "danger")
|
||||
return redirect(url_for("dhcp.overview"))
|
||||
|
||||
@@ -549,20 +549,13 @@ def render_config(
|
||||
# Server tatsaechlich sendet. Es wird nicht gespeichert, sondern aus dem
|
||||
# Katalog abgeleitet -- gespeichert waere es eine zweite Wahrheit.
|
||||
belegte_codes = {int(row["code"]) for row in option_rows}
|
||||
gesendete_ids = set(global_values) | {
|
||||
option_id for werte in device_values.values() for option_id in werte
|
||||
}
|
||||
verzeichnis = " ".join(
|
||||
f"{int(row['code'])}:{row['name']}:{row['type']}"
|
||||
for row in option_rows
|
||||
if not int(row["is_standard"] or 0) and int(row["id"]) in gesendete_ids
|
||||
)
|
||||
if verzeichnis and KATALOG_CODE not in belegte_codes:
|
||||
eintrag = verzeichnis(conn)
|
||||
if eintrag and KATALOG_CODE not in belegte_codes:
|
||||
custom_defs.append(
|
||||
{"name": KATALOG_NAME, "code": KATALOG_CODE, "type": "string", "space": "dhcp4"}
|
||||
)
|
||||
global_option_data.append({"name": KATALOG_NAME, "data": verzeichnis})
|
||||
elif verzeichnis:
|
||||
global_option_data.append({"name": KATALOG_NAME, "data": eintrag})
|
||||
elif eintrag:
|
||||
warnings.append(
|
||||
f"Code {KATALOG_CODE} ist von einer eigenen Option belegt | das Verzeichnis für "
|
||||
"die Clients wird nicht mitgesendet. Neue Optionen sind dort dann nur unter "
|
||||
@@ -993,6 +986,20 @@ echo "ergaenzt: $ziel"
|
||||
"""
|
||||
|
||||
|
||||
def verzeichnis(conn: sqlite3.Connection) -> str:
|
||||
"""Das Verzeichnis, das der Server seinen Clients mitsendet.
|
||||
|
||||
Eine Quelle fuer beides -- die Konfiguration und die Anzeige. Zweimal
|
||||
berechnet waeren es zwei Wahrheiten, und die Seite wuerde irgendwann etwas
|
||||
anderes zeigen als der Server sendet.
|
||||
"""
|
||||
return " ".join(
|
||||
f"{int(zeile['code'])}:{zeile['name']}:{zeile['type']}"
|
||||
for zeile in gesendete_optionen(conn)
|
||||
if not int(zeile["is_standard"] or 0)
|
||||
)
|
||||
|
||||
|
||||
def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
||||
"""Was ein Linux-Client braucht, damit die Optionen bei ihm ankommen.
|
||||
|
||||
|
||||
@@ -230,15 +230,18 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% set gesetzte = options | selectattr('value') | list %}
|
||||
{# "gesetzt" heisst nicht nur "global gesetzt": ein Wert an einem einzelnen
|
||||
Geraet wird genauso gesendet. Die Liste kommt deshalb fertig vom Server --
|
||||
in Jinja gerechnet zaehlte sie nur die globalen und meldete "Nichts
|
||||
gesetzt", waehrend die Vorschau daneben die Option auswies. #}
|
||||
|
||||
<section class="card">
|
||||
<div class="card__header">
|
||||
<div class="flex-1">
|
||||
<div class="card__title">{{ icon('puzzle', size=17) }} Gesendete Optionen</div>
|
||||
<div class="card__sub">
|
||||
{% if gesetzte %}
|
||||
<strong>{{ gesetzte | length }}</strong> von {{ options | length }} Optionen tragen
|
||||
{% if gesendet %}
|
||||
<strong>{{ gesendet | length }}</strong> von {{ options | length }} Optionen tragen
|
||||
einen Wert. Was leer ist, wird nicht gesendet.
|
||||
{% else %}
|
||||
Derzeit trägt keine Option einen Wert – es wird also nichts über die
|
||||
@@ -248,7 +251,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if gesetzte %}
|
||||
{% if gesendet %}
|
||||
<div class="table-wrap">
|
||||
<table class="table table--compact">
|
||||
<thead><tr>
|
||||
@@ -256,7 +259,7 @@
|
||||
<th style="width:42%">Wert</th><th class="actions"></th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for option in gesetzte %}
|
||||
{% for option in gesendet %}
|
||||
<tr>
|
||||
<td class="numeric fs-sm">{{ option.code }}</td>
|
||||
<td class="fs-sm">
|
||||
@@ -264,7 +267,19 @@
|
||||
<div class="fs-xs faint">{{ option.description }}</div>
|
||||
</td>
|
||||
<td class="fs-xs mono">{{ option.type }}</td>
|
||||
<td>{{ optionswert(option) }}</td>
|
||||
<td>
|
||||
{{ optionswert(option) }}
|
||||
{% if option.geraete %}
|
||||
<div class="fs-xs faint mt-2">
|
||||
{% for eintrag in option.geraete %}
|
||||
<div>
|
||||
nur für <strong>{{ eintrag.geraet }}</strong>:
|
||||
<span class="mono">{{ eintrag.value }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="actions">{{ optionsloeschen(option) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -284,7 +299,7 @@
|
||||
<div class="flex-1">
|
||||
<div class="card__title">{{ icon('list', size=17) }} Optionskatalog</div>
|
||||
<div class="card__sub">
|
||||
Alle {{ options | length }} bekannten Optionen. Standardoptionen sind nicht löschbar,
|
||||
Alle {{ katalog | length }} bekannten Optionen. Standardoptionen sind nicht löschbar,
|
||||
ihr Wert lässt sich leeren.
|
||||
</div>
|
||||
</div>
|
||||
@@ -297,7 +312,7 @@
|
||||
<th style="width:42%">Wert</th><th class="actions"></th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for option in options %}
|
||||
{% for option in katalog %}
|
||||
<tr data-filter-text="{{ option.code }} {{ option.name }} {{ option.description }}">
|
||||
<td class="numeric fs-sm">{{ option.code }}</td>
|
||||
<td class="fs-sm">
|
||||
@@ -305,8 +320,14 @@
|
||||
<div class="fs-xs faint">{{ option.description }}</div>
|
||||
</td>
|
||||
<td class="fs-xs mono">{{ option.type }}</td>
|
||||
{% if option.reserviert %}
|
||||
{# Kein Eingabefeld: den Wert bestimmt der Katalog selbst. #}
|
||||
<td class="fs-xs mono faint">{{ option.value or '(noch keine eigene Option)' }}</td>
|
||||
<td class="actions">{{ pill('belegt', '') }}</td>
|
||||
{% else %}
|
||||
<td>{{ optionswert(option) }}</td>
|
||||
<td class="actions">{{ optionsloeschen(option) }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
Reference in new issue
Block a user