DHCP: Subnetze deaktivierbar + 47 verifizierte Standard-Optionen
1. Subnetze koennen einzeln deaktiviert werden (enabled-Spalte), ohne sie loeschen zu muessen -- werden dann bei "In Datei schreiben" und "Aktivieren & (neu) starten" komplett uebersprungen. Bewusst nicht als "ungueltig" gewertet (kein Fehler/keine Warnung), da eine deaktivierte Zeile absichtlich kein echtes Netz braucht. Toggle-Button in der Subnetz-Tabelle, deaktivierte Zeilen abgeblendet mit "Deaktiviert"-Pill. 2. 47 kuratierte Standard-DHCP-Optionen vorbefuellt (analog den "Predefined Options" eines Windows-DHCP-Servers) -- Router/DNS-Server/ Domain-Name/Lease-Zeiten ausgenommen, dafuer gibt es bereits eigene Felder. Alle Namen live gegen eine echte Kea-2.4.1-Instanz verifiziert: jede der 49 Kandidaten-Optionen einzeln per kea-dhcp4 -t getestet, 47 akzeptiert (2 falsch geratene Namen verworfen: nis-domain-name statt nis-domain, nisplus-domain statt nisplus-domain-name). Wichtig fuer Kea-Kompatibilitaet: Standard-Optionen bekommen KEIN eigenes option-def in der generierten Konfiguration (neue is_standard- Spalte in dhcp_option_defs) -- Kea kennt sie bereits nativ, eine Neudefinition haette die eingebaute dupliziert. Nur echte eigene/ herstellerspezifische Options bekommen weiterhin ein option-def. Live verifiziert: option-def bleibt bei gesetztem Standard-Options-Wert leer, option-data enthaelt den Wert korrekt, kea-dhcp4 -t besteht, echter Dienstneustart mit der Config erfolgreich. Einmalige Migration (Guard wie bei den anderen Schema-Migrationen) -- ein vom Admin bewusst geloeschter Standard-Eintrag wird nach einem Neustart nicht stillschweigend wieder angelegt. Ausserdem in dieser Session zuvor gefixt (siehe vorherige Commits): der falsche Kea-Schluessel lease-file->name, der jeden Dienststart blockierte, sowie der 'Jetzt pruefen'-Button, der den Topbar-Countdown nur auf dem Dashboard aktualisierte.
This commit is contained in:
@@ -265,15 +265,32 @@ der aktiv weiterentwickelte Nachfolger und bildet „globaler Wert, pro Client
|
|||||||
abzubrechen) und bei „Aktivieren & (neu) starten“ (verweigert den Start,
|
abzubrechen) und bei „Aktivieren & (neu) starten“ (verweigert den Start,
|
||||||
falls kein einziges gültiges Subnetz mehr existiert). Live auf einer
|
falls kein einziges gültiges Subnetz mehr existiert). Live auf einer
|
||||||
echten Fehlkonfiguration reproduziert und verifiziert.
|
echten Fehlkonfiguration reproduziert und verifiziert.
|
||||||
- **Eigene Options** (`dhcp_option_defs`/`dhcp_option_values`, analog den
|
- **Subnetze einzeln deaktivierbar**: ein Subnetz lässt sich ohne Löschen
|
||||||
„Vordefinierten Optionen und Werten“ eines Windows-DHCP-Servers): Code,
|
pausieren (`enabled`-Flag) — wird dann bei „In Datei schreiben“ und
|
||||||
Name, Kea-Datentyp und Beschreibung frei definierbar (z.B. herstellerspezifische
|
„Aktivieren & (neu) starten“ komplett ignoriert (kein Fehler, bewusst
|
||||||
Options wie eine Terminal-Boot-URL). Jede Option hat einen **globalen**
|
kein "ungültig", da das Fehlen des Netzes hier Absicht ist), statt es
|
||||||
Wert sowie optional einen **Wert pro Client** — ein Client-Override
|
jedes Mal neu anlegen zu müssen.
|
||||||
überschreibt den globalen Wert ausschließlich für dieses eine Gerät
|
- **DHCP-Options** (`dhcp_option_defs`/`dhcp_option_values`): eine kuratierte
|
||||||
(technisch: globale Werte landen im Top-Level `option-data`, Overrides im
|
Auswahl an **47 Standard-Optionen** (analog den „Vordefinierten Optionen“
|
||||||
`option-data` der jeweiligen Kea-Reservierung, was Kea automatisch pro
|
eines Windows-DHCP-Servers — z.B. NTP-/WINS-/TFTP-/SMTP-Server, Boot-Datei,
|
||||||
Client Vorrang geben lässt).
|
Domain-Search, IP-Forwarding) ist automatisch vorbefüllt; Router/DNS-Server/
|
||||||
|
Domain-Name/Lease-Zeiten sind bewusst ausgenommen, da dafür bereits eigene,
|
||||||
|
dedizierte Felder existieren (Subnetze bzw. globale Einstellungen oben).
|
||||||
|
Alle 47 Namen sind live gegen eine echte Kea-2.4.1-Instanz verifiziert
|
||||||
|
(`kea-dhcp4 -t`, jede Option einzeln getestet). Standard-Optionen bekommen
|
||||||
|
bewusst **kein eigenes `option-def`** in der generierten Konfiguration —
|
||||||
|
Kea kennt sie bereits nativ, eine Neudefinition würde die eingebaute
|
||||||
|
duplizieren; nur zusätzlich angelegte, wirklich eigene/herstellerspezifische
|
||||||
|
Options (z.B. eine Terminal-Boot-URL) bekommen eines. Beide Arten
|
||||||
|
funktionieren im UI identisch: Code, Name, Kea-Datentyp und Beschreibung
|
||||||
|
frei definierbar, jede Option hat einen **globalen** Wert sowie optional
|
||||||
|
einen **Wert pro Client** — ein Client-Override überschreibt den globalen
|
||||||
|
Wert ausschließlich für dieses eine Gerät (technisch: globale Werte landen
|
||||||
|
im Top-Level `option-data`, Overrides im `option-data` der jeweiligen
|
||||||
|
Kea-Reservierung, was Kea automatisch pro Client Vorrang geben lässt).
|
||||||
|
Ein versehentlich vom Admin gelöschter Standard-Eintrag wird nach einem
|
||||||
|
Neustart nicht automatisch wieder angelegt (einmalige Migration, kein
|
||||||
|
Zurückschreiben bei jedem Start).
|
||||||
- **Reservierungen**: für jedes aktive Gerät mit gültiger MAC + IP wird eine
|
- **Reservierungen**: für jedes aktive Gerät mit gültiger MAC + IP wird eine
|
||||||
Kea-Reservierung im **passenden Subnetz** erzeugt (die IP entscheidet, zu
|
Kea-Reservierung im **passenden Subnetz** erzeugt (die IP entscheidet, zu
|
||||||
welchem der ggf. mehreren konfigurierten Subnetze ein Gerät gehört); der
|
welchem der ggf. mehreren konfigurierten Subnetze ein Gerät gehört); der
|
||||||
|
|||||||
+122
-7
@@ -491,6 +491,64 @@ def get_db_connection():
|
|||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
# Kuratierte Standard-DHCPv4-Optionen (analog den "Predefined Options" eines
|
||||||
|
# Windows-DHCP-Servers) — vorbefüllt in _ensure_schema(), siehe dort für die
|
||||||
|
# Begründung. Muss vor dem _ensure_schema()-Aufruf am Ende der Datei stehen.
|
||||||
|
# (code, Kea-Name, Anzeige-Typ, Beschreibung). Router (3)/DNS-Server (6)/
|
||||||
|
# Domain-Name (15)/Lease-Zeiten (51/58/59) sind bereits eigene, dedizierte
|
||||||
|
# Felder und deshalb bewusst nicht enthalten. Alle 47 Namen live gegen eine
|
||||||
|
# echte Kea-2.4.1-Instanz verifiziert (kea-dhcp4 -t je Option einzeln).
|
||||||
|
DHCP_STANDARD_OPTIONS = [
|
||||||
|
(4, "time-servers", "ipv4-address", "Zeitserver (RFC 868)"),
|
||||||
|
(7, "log-servers", "ipv4-address", "Log-Server (MIT-LCS UDP)"),
|
||||||
|
(8, "cookie-servers", "ipv4-address", "Cookie-Server"),
|
||||||
|
(9, "lpr-servers", "ipv4-address", "LPR-Druckserver"),
|
||||||
|
(10, "impress-servers", "ipv4-address", "Impress-Server"),
|
||||||
|
(11, "resource-location-servers", "ipv4-address", "Resource Location Server"),
|
||||||
|
(16, "swap-server", "ipv4-address", "Swap-Server"),
|
||||||
|
(17, "root-path", "string", "Root-Pfad (NFS)"),
|
||||||
|
(18, "extensions-path", "string", "Extensions-Pfad (BOOTP)"),
|
||||||
|
(19, "ip-forwarding", "boolean", "IP-Forwarding aktivieren"),
|
||||||
|
(20, "non-local-source-routing", "boolean", "Non-Local Source Routing"),
|
||||||
|
(26, "interface-mtu", "uint16", "MTU des Interfaces"),
|
||||||
|
(27, "all-subnets-local", "boolean", "Alle Subnetze lokal erreichbar"),
|
||||||
|
(28, "broadcast-address", "ipv4-address", "Broadcast-Adresse"),
|
||||||
|
(29, "perform-mask-discovery", "boolean", "Subnetzmasken-Discovery"),
|
||||||
|
(30, "mask-supplier", "boolean", "Subnetzmasken-Anbieter"),
|
||||||
|
(31, "router-discovery", "boolean", "Router Discovery (RFC 1256)"),
|
||||||
|
(32, "router-solicitation-address", "ipv4-address", "Router-Solicitation-Adresse"),
|
||||||
|
(33, "static-routes", "string", "Statische Routen (Ziel,Gateway,...)"),
|
||||||
|
(34, "trailer-encapsulation", "boolean", "Trailer Encapsulation"),
|
||||||
|
(35, "arp-cache-timeout", "uint32", "ARP-Cache-Timeout (Sek.)"),
|
||||||
|
(36, "ieee802-3-encapsulation", "boolean", "Ethernet-Encapsulation (IEEE 802.3)"),
|
||||||
|
(37, "default-tcp-ttl", "uint8", "Standard-TTL für TCP"),
|
||||||
|
(38, "tcp-keepalive-interval", "uint32", "TCP-Keepalive-Intervall (Sek.)"),
|
||||||
|
(40, "nis-domain", "string", "NIS-Domain"),
|
||||||
|
(41, "nis-servers", "ipv4-address", "NIS-Server"),
|
||||||
|
(42, "ntp-servers", "ipv4-address", "NTP-Server"),
|
||||||
|
(44, "netbios-name-servers", "ipv4-address", "WINS/NBNS-Server"),
|
||||||
|
(45, "netbios-dd-server", "ipv4-address", "NetBIOS Datagram Distribution Server"),
|
||||||
|
(46, "netbios-node-type", "uint8", "WINS/NBT-Knotentyp"),
|
||||||
|
(47, "netbios-scope", "string", "NetBIOS-Scope-ID"),
|
||||||
|
(48, "font-servers", "ipv4-address", "X-Window-Font-Server"),
|
||||||
|
(49, "x-display-manager", "ipv4-address", "X-Window Display Manager"),
|
||||||
|
(64, "nisplus-domain-name", "string", "NIS+-Domain"),
|
||||||
|
(65, "nisplus-servers", "ipv4-address", "NIS+-Server"),
|
||||||
|
(66, "tftp-server-name", "string", "Boot-Server (TFTP-Hostname)"),
|
||||||
|
(67, "boot-file-name", "string", "Bootdatei-Name"),
|
||||||
|
(68, "mobile-ip-home-agent", "ipv4-address", "Mobile-IP-Home-Agent"),
|
||||||
|
(69, "smtp-server", "ipv4-address", "SMTP-Server"),
|
||||||
|
(70, "pop-server", "ipv4-address", "POP3-Server"),
|
||||||
|
(71, "nntp-server", "ipv4-address", "NNTP-Server"),
|
||||||
|
(72, "www-server", "ipv4-address", "WWW-Server"),
|
||||||
|
(73, "finger-server", "ipv4-address", "Finger-Server"),
|
||||||
|
(74, "irc-server", "ipv4-address", "IRC-Server"),
|
||||||
|
(75, "streettalk-server", "ipv4-address", "StreetTalk-Server"),
|
||||||
|
(76, "streettalk-directory-assistance-server", "ipv4-address", "StreetTalk Directory Assistance Server"),
|
||||||
|
(119, "domain-search", "string", "DNS-Suchliste (Domain Search)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _ensure_schema():
|
def _ensure_schema():
|
||||||
"""Legt neue Tabellen (Gruppen/Rechte) an, falls die DB noch aus einer
|
"""Legt neue Tabellen (Gruppen/Rechte) an, falls die DB noch aus einer
|
||||||
älteren Version stammt — idempotent, sicher bei jedem Start aufzurufen.
|
älteren Version stammt — idempotent, sicher bei jedem Start aufzurufen.
|
||||||
@@ -590,9 +648,15 @@ def _ensure_schema():
|
|||||||
code INTEGER NOT NULL UNIQUE,
|
code INTEGER NOT NULL UNIQUE,
|
||||||
name TEXT NOT NULL UNIQUE,
|
name TEXT NOT NULL UNIQUE,
|
||||||
type TEXT NOT NULL,
|
type TEXT NOT NULL,
|
||||||
description TEXT
|
description TEXT,
|
||||||
|
is_standard INTEGER NOT NULL DEFAULT 0
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
# Migration: "is_standard"-Spalte nachrüsten, falls die Tabelle noch aus
|
||||||
|
# einer Version ohne vorbefüllte Standard-Optionen stammt.
|
||||||
|
option_def_cols = {row["name"] for row in conn.execute("PRAGMA table_info(dhcp_option_defs)").fetchall()}
|
||||||
|
if "is_standard" not in option_def_cols:
|
||||||
|
conn.execute("ALTER TABLE dhcp_option_defs ADD COLUMN is_standard INTEGER NOT NULL DEFAULT 0")
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS dhcp_option_values (
|
CREATE TABLE IF NOT EXISTS dhcp_option_values (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -603,6 +667,26 @@ def _ensure_schema():
|
|||||||
UNIQUE (option_def_id, device_mac)
|
UNIQUE (option_def_id, device_mac)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
# Einmalig eine kuratierte Auswahl an Standard-DHCP-Optionen vorbefüllen
|
||||||
|
# (analog den "Predefined Options" eines Windows-DHCP-Servers) — Router/
|
||||||
|
# DNS-Server/Domain-Name/Lease-Zeiten sind bereits eigene, dedizierte
|
||||||
|
# Felder (siehe dhcp_subnets/DHCP_DEFAULTS) und deshalb hier bewusst
|
||||||
|
# ausgenommen. Alle Namen live gegen eine echte Kea-2.4.1-Instanz
|
||||||
|
# verifiziert (kea-dhcp4 -t) — Kea kennt diese Standard-Optionen bereits
|
||||||
|
# nativ, sie bekommen deshalb (anders als eigene Options) KEIN eigenes
|
||||||
|
# option-def in der generierten Konfiguration (siehe _render_kea_config),
|
||||||
|
# sonst würde die eingebaute Definition dupliziert. Per Guard nur
|
||||||
|
# einmalig, damit ein bewusst vom Admin gelöschter Eintrag nach einem
|
||||||
|
# Neustart nicht stillschweigend wieder auftaucht.
|
||||||
|
_migration_key_std_opts = "_seeded_dhcp_standard_options_v1"
|
||||||
|
if not conn.execute("SELECT 1 FROM settings WHERE key=?", (_migration_key_std_opts,)).fetchone():
|
||||||
|
for code, name, opt_type, description in DHCP_STANDARD_OPTIONS:
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR IGNORE INTO dhcp_option_defs (code, name, type, description, is_standard) "
|
||||||
|
"VALUES (?, ?, ?, ?, 1)",
|
||||||
|
(code, name, opt_type, description),
|
||||||
|
)
|
||||||
|
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, '1')", (_migration_key_std_opts,))
|
||||||
|
|
||||||
# DHCP: mehrere gleichzeitige Subnetze statt einer einzelnen globalen
|
# DHCP: mehrere gleichzeitige Subnetze statt einer einzelnen globalen
|
||||||
# Range — ein Host kann mehrere IPs/Interfaces mit jeweils eigenem Netz
|
# Range — ein Host kann mehrere IPs/Interfaces mit jeweils eigenem Netz
|
||||||
@@ -618,9 +702,15 @@ def _ensure_schema():
|
|||||||
range_end TEXT NOT NULL,
|
range_end TEXT NOT NULL,
|
||||||
gateway TEXT,
|
gateway TEXT,
|
||||||
dns TEXT,
|
dns TEXT,
|
||||||
sort_order INTEGER NOT NULL DEFAULT 0
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
# Migration: "enabled"-Spalte nachrüsten, falls die Tabelle noch aus
|
||||||
|
# einer Version ohne Deaktivieren-Funktion stammt.
|
||||||
|
dhcp_subnet_cols = {row["name"] for row in conn.execute("PRAGMA table_info(dhcp_subnets)").fetchall()}
|
||||||
|
if "enabled" not in dhcp_subnet_cols:
|
||||||
|
conn.execute("ALTER TABLE dhcp_subnets ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1")
|
||||||
# Migration: bisherige globale Einzel-Range (dhcp_interface/_range_*/
|
# Migration: bisherige globale Einzel-Range (dhcp_interface/_range_*/
|
||||||
# _gateway/_dns in der settings-Tabelle) einmalig in eine erste Zeile
|
# _gateway/_dns in der settings-Tabelle) einmalig in eine erste Zeile
|
||||||
# der neuen Tabelle überführen, statt eine bereits funktionierende
|
# der neuen Tabelle überführen, statt eine bereits funktionierende
|
||||||
@@ -1912,11 +2002,16 @@ def _dhcp_valid_subnets(subnets):
|
|||||||
"""Nimmt die rohen dhcp_subnets-Zeilen und validiert JEDE einzeln gegen
|
"""Nimmt die rohen dhcp_subnets-Zeilen und validiert JEDE einzeln gegen
|
||||||
ihr tatsächliches Netz (siehe _dhcp_subnet_matches — es muss eine
|
ihr tatsächliches Netz (siehe _dhcp_subnet_matches — es muss eine
|
||||||
physische IP im gewünschten Bereich vorhanden sein, sonst kann Kea
|
physische IP im gewünschten Bereich vorhanden sein, sonst kann Kea
|
||||||
damit nichts anfangen). Gibt (valid, invalid) zurück: valid ist um
|
damit nichts anfangen). Deaktivierte Subnetze (enabled=0) werden dabei
|
||||||
"net_info" angereichert (für Reservierungs-Zuordnung und Kea-Config),
|
komplett übersprungen — bewusst nicht als "invalid" gewertet, da das
|
||||||
invalid enthält (Zeile, Fehlertext) für die Anzeige/Warnungen."""
|
Fehlen eines echten Netzes hier kein Fehler ist, sondern Absicht.
|
||||||
|
Gibt (valid, invalid) zurück: valid ist um "net_info" angereichert (für
|
||||||
|
Reservierungs-Zuordnung und Kea-Config), invalid enthält (Zeile,
|
||||||
|
Fehlertext) für die Anzeige/Warnungen."""
|
||||||
valid, invalid = [], []
|
valid, invalid = [], []
|
||||||
for s in subnets:
|
for s in subnets:
|
||||||
|
if not s["enabled"]:
|
||||||
|
continue
|
||||||
net_info, error = _dhcp_subnet_matches(s)
|
net_info, error = _dhcp_subnet_matches(s)
|
||||||
if error:
|
if error:
|
||||||
invalid.append((s, error))
|
invalid.append((s, error))
|
||||||
@@ -1975,7 +2070,9 @@ def _dhcp_reservation_candidates(conn, valid_subnets):
|
|||||||
|
|
||||||
|
|
||||||
def _dhcp_option_defs(conn):
|
def _dhcp_option_defs(conn):
|
||||||
return conn.execute("SELECT id, code, name, type, description FROM dhcp_option_defs ORDER BY code ASC").fetchall()
|
return conn.execute(
|
||||||
|
"SELECT id, code, name, type, description, is_standard FROM dhcp_option_defs ORDER BY code ASC"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
|
||||||
def _dhcp_option_values(conn):
|
def _dhcp_option_values(conn):
|
||||||
@@ -2045,9 +2142,13 @@ def _render_kea_config(cfg, valid_subnets, reservations_by_subnet, option_defs,
|
|||||||
Reservierung — Kea überschreibt damit automatisch gleichnamige
|
Reservierung — Kea überschreibt damit automatisch gleichnamige
|
||||||
globale/Subnet-Options für genau diesen Client, ohne dass wir das
|
globale/Subnet-Options für genau diesen Client, ohne dass wir das
|
||||||
selbst zusammenrechnen müssen."""
|
selbst zusammenrechnen müssen."""
|
||||||
|
# Standard-Optionen (is_standard) bekommen KEIN eigenes option-def — Kea
|
||||||
|
# kennt sie bereits nativ, eine Neudefinition würde die eingebaute
|
||||||
|
# duplizieren (siehe DHCP_STANDARD_OPTIONS). Nur eigene Options brauchen
|
||||||
|
# ein option-def, damit Kea Name/Typ/Code überhaupt kennt.
|
||||||
option_def_list = [
|
option_def_list = [
|
||||||
{"name": d["name"], "code": d["code"], "type": d["type"], "space": "dhcp4"}
|
{"name": d["name"], "code": d["code"], "type": d["type"], "space": "dhcp4"}
|
||||||
for d in option_defs
|
for d in option_defs if not d["is_standard"]
|
||||||
]
|
]
|
||||||
|
|
||||||
global_option_data = []
|
global_option_data = []
|
||||||
@@ -2201,6 +2302,20 @@ def settings_dhcp():
|
|||||||
log_action("settings.dhcp_subnet_delete", subnet_id)
|
log_action("settings.dhcp_subnet_delete", subnet_id)
|
||||||
flash("Subnetz gelöscht.", "success")
|
flash("Subnetz gelöscht.", "success")
|
||||||
|
|
||||||
|
elif "toggle_dhcp_subnet" in request.form:
|
||||||
|
subnet_id = request.form.get("toggle_dhcp_subnet")
|
||||||
|
row = conn.execute("SELECT enabled, interface FROM dhcp_subnets WHERE id=?", (subnet_id,)).fetchone()
|
||||||
|
if row:
|
||||||
|
new_state = 0 if row["enabled"] else 1
|
||||||
|
conn.execute("UPDATE dhcp_subnets SET enabled=? WHERE id=?", (new_state, subnet_id))
|
||||||
|
conn.commit()
|
||||||
|
log_action("settings.dhcp_subnet_toggle", row["interface"], "aktiviert" if new_state else "deaktiviert")
|
||||||
|
flash(
|
||||||
|
"Subnetz aktiviert." if new_state else
|
||||||
|
"Subnetz deaktiviert — wird beim nächsten Schreiben/Starten nicht mehr berücksichtigt.",
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
|
||||||
elif "add_dhcp_option" in request.form:
|
elif "add_dhcp_option" in request.form:
|
||||||
code_raw = request.form.get("code", "").strip()
|
code_raw = request.form.get("code", "").strip()
|
||||||
name = request.form.get("name", "").strip()
|
name = request.form.get("name", "").strip()
|
||||||
|
|||||||
@@ -113,13 +113,15 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for s in subnets %}
|
{% for s in subnets %}
|
||||||
{% set st = subnet_status.get(s.id) %}
|
{% set st = subnet_status.get(s.id) %}
|
||||||
<tr>
|
<tr {% if not s.enabled %}style="opacity:.55;"{% endif %}>
|
||||||
<td class="mono">{{ s.interface }}</td>
|
<td class="mono">{{ s.interface }}</td>
|
||||||
<td class="mono">{{ s.range_start }} – {{ s.range_end }}</td>
|
<td class="mono">{{ s.range_start }} – {{ s.range_end }}</td>
|
||||||
<td class="mono text-faint">{{ s.gateway or '(automatisch)' }}</td>
|
<td class="mono text-faint">{{ s.gateway or '(automatisch)' }}</td>
|
||||||
<td class="mono text-faint">{{ s.dns or '—' }}</td>
|
<td class="mono text-faint">{{ s.dns or '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if st and st.ok %}
|
{% if not s.enabled %}
|
||||||
|
<span class="pill disabled">Deaktiviert</span>
|
||||||
|
{% elif st and st.ok %}
|
||||||
<span class="pill online">Erkannt</span>
|
<span class="pill online">Erkannt</span>
|
||||||
<span class="mono text-faint" style="font-size:11.5px;">{{ st.network }}</span>
|
<span class="mono text-faint" style="font-size:11.5px;">{{ st.network }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -130,6 +132,16 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="toggle_dhcp_subnet" value="{{ s.id }}">
|
||||||
|
<button type="submit" class="icon-btn" title="{{ 'Deaktivieren' if s.enabled else 'Aktivieren' }}">
|
||||||
|
{% if s.enabled %}
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M4.9 4.9l14.2 14.2"/></svg>
|
||||||
|
{% else %}
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
<button type="button" class="icon-btn" title="Bearbeiten" data-open-modal="editSubnetModal{{ loop.index }}">
|
<button type="button" class="icon-btn" title="Bearbeiten" data-open-modal="editSubnetModal{{ loop.index }}">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4z"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -156,8 +168,11 @@
|
|||||||
<div class="card card-pad" style="grid-column:1 / -1;">
|
<div class="card card-pad" style="grid-column:1 / -1;">
|
||||||
<div class="section-head" style="margin-bottom:16px;">
|
<div class="section-head" style="margin-bottom:16px;">
|
||||||
<div>
|
<div>
|
||||||
<h2 style="font-size:16px;">Eigene DHCP-Options</h2>
|
<h2 style="font-size:16px;">DHCP-Options</h2>
|
||||||
<div class="hint">Herstellerspezifische Options, global oder pro Client überschreibbar.</div>
|
<div class="hint">
|
||||||
|
Standard-Optionen (analog den "Predefined Options" eines Windows-DHCP-Servers) sind bereits vorbefüllt,
|
||||||
|
eigene/herstellerspezifische Options lassen sich zusätzlich anlegen — beide global oder pro Client überschreibbar.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<button type="button" class="btn btn-primary" data-open-modal="addOptionModal">
|
<button type="button" class="btn btn-primary" data-open-modal="addOptionModal">
|
||||||
@@ -171,13 +186,20 @@
|
|||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<div style="overflow-x:auto;">
|
<div style="overflow-x:auto;">
|
||||||
<table class="data-table">
|
<table class="data-table">
|
||||||
<thead><tr><th>Code</th><th>Name</th><th>Typ</th><th>Beschreibung</th><th>Globaler Wert</th><th style="width:1%;">Aktionen</th></tr></thead>
|
<thead><tr><th>Code</th><th>Name</th><th>Typ</th><th>Herkunft</th><th>Beschreibung</th><th>Globaler Wert</th><th style="width:1%;">Aktionen</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for d in option_defs %}
|
{% for d in option_defs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="mono">{{ d.code }}</td>
|
<td class="mono">{{ d.code }}</td>
|
||||||
<td class="mono">{{ d.name }}</td>
|
<td class="mono">{{ d.name }}</td>
|
||||||
<td>{{ d.type }}</td>
|
<td>{{ d.type }}</td>
|
||||||
|
<td>
|
||||||
|
{% if d.is_standard %}
|
||||||
|
<span class="pill" style="background:var(--muted-dim); color:var(--text-faint);">Standard</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="pill user">Eigen</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td class="text-faint" style="font-size:12px;">{{ d.description or '—' }}</td>
|
<td class="text-faint" style="font-size:12px;">{{ d.description or '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
|
|||||||
Reference in New Issue
Block a user