Neu: Netzwerkeinstellungen des Hosts unter Systemeinstellungen (IP/DNS/DHCP)
Bisher enthielt "Systemeinstellungen" nur das Pruefintervall. Jetzt zusaetzlich Verwaltung der Netzwerkkonfiguration DIESES Hosts (getrennt vom Kea-DHCP-*Server* fuer Clients): - Backend-Erkennung (NetworkManager vs. dhcpcd via systemctl is-active). Kein erkanntes Backend -> Seite bleibt rein lesend statt zu raten. - Status-Anzeige: Interface, IP/Prefix, Gateway, DNS, Modus (DHCP/Statisch). - Aenderung: Interface, Modus, bei Statisch IP/Prefix/Gateway, DNS unabhaengig von Modus setzbar (IP per DHCP + feste DNS moeglich). - Sicherheitsnetz analog "netplan try": Backup vor jeder Aenderung, automatisches Rollback nach 45s ohne explizite Bestaetigung (nur moeglich, wenn die Seite ueber die neue Config noch erreichbar ist) -- verhindert Aussperren durch einen Tippfehler bei IP/Gateway. - Bugfix waehrend der Implementierung gefunden: der Auto-Revert-Timer laeuft in einem Hintergrund-Thread ohne Request-Kontext: log_action() griff auf current_user zu und warf dort einen AttributeError (die Config wurde trotzdem korrekt zurueckgerollt, nur der Audit-Log-Eintrag fehlte und ein Fehler landete im Server-Log). Fix: log_action_system() ohne current_user-Abhaengigkeit fuer Code ausserhalb des Request- Kontexts. - Getestet: Backend-Erkennung + rein lesender Fallback live (WSL hat weder NetworkManager noch dhcpcd aktiv, korrekt erkannt), Rechte-Gating, Anwenden-/Rollback-Logik fuer beide Backends per gemocktem subprocess.run verifiziert (inkl. echtem dhcpcd.conf-Rewrite und vollständigem Anwenden-dann-Auto-Rollback-Durchlauf mit Audit-Log- Eintrag). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+266
-7
@@ -813,6 +813,20 @@ def log_action(action, target=None, details=None):
|
||||
conn.close()
|
||||
|
||||
|
||||
def log_action_system(action, target=None, details=None):
|
||||
"""Wie log_action(), aber ohne Abhängigkeit von current_user — für
|
||||
Code, der außerhalb eines Request-Kontexts läuft (z.B. der
|
||||
Netzwerk-Auto-Revert-Timer in einem eigenen Thread, wo current_user
|
||||
nicht auflösbar ist und einen AttributeError werfen würde)."""
|
||||
conn = get_db_connection()
|
||||
conn.execute(
|
||||
"INSERT INTO audit_log (ts, username, action, target, details) VALUES (?, ?, ?, ?, ?)",
|
||||
(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "system", action, target, details),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def touch_record(conn, table, key_col, key_val):
|
||||
"""Trägt 'zuletzt geändert von/am' direkt am Datensatz ein (Devices/Switches)."""
|
||||
who = current_user.username if current_user.is_authenticated else "system"
|
||||
@@ -1214,6 +1228,192 @@ def run_check_now():
|
||||
# Settings
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# System-Netzwerkeinstellungen (IP/DNS/DHCP DIESES Hosts — nicht zu
|
||||
# verwechseln mit dem Kea-DHCP-*Server* für Client-Geräte weiter unten).
|
||||
#
|
||||
# Höchste Vorsicht geboten: eine falsche Änderung hier kann die Erreichbarkeit
|
||||
# dieses Hosts (SSH/Web-UI) komplett kappen. Deshalb:
|
||||
# - Erkennung des tatsächlich aktiven Netzwerk-Backends (NetworkManager
|
||||
# oder dhcpcd) statt blind eine Methode anzunehmen; ist keins von beiden
|
||||
# aktiv (z.B. in dieser WSL-Testumgebung), bleibt die Seite bewusst rein
|
||||
# lesend statt zu raten.
|
||||
# - Vor jeder Änderung wird die vorherige Konfiguration gesichert.
|
||||
# - Nach dem Anwenden läuft ein Sicherheits-Timer im Hintergrund: wird die
|
||||
# neue Konfiguration nicht innerhalb von NETWORK_REVERT_SECONDS explizit
|
||||
# bestätigt (das ist nur möglich, wenn die Web-UI über die NEUE
|
||||
# Konfiguration noch erreichbar ist), wird automatisch die gesicherte
|
||||
# Konfiguration wiederhergestellt — analog zu "netplan try".
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
NETWORK_REVERT_SECONDS = 45
|
||||
_pending_network_revert = {} # {"timer": Timer, "backend":..., "interface":..., "backup": {...}}
|
||||
|
||||
|
||||
def _detect_network_backend():
|
||||
"""Rein lesend: welcher der beiden gängigen Netzwerk-Manager auf
|
||||
Debian/Raspberry Pi OS ist aktiv? Keine Annahme, falls keins von
|
||||
beiden läuft (z.B. in dieser WSL-Testumgebung, die ihr Netz über den
|
||||
Hyper-V-Adapter bezieht) — dann bleibt die Seite bewusst rein lesend."""
|
||||
for service, name in (("NetworkManager", "networkmanager"), ("dhcpcd", "dhcpcd")):
|
||||
try:
|
||||
result = subprocess.run(["systemctl", "is-active", service], capture_output=True, text=True, timeout=5)
|
||||
if result.stdout.strip() == "active":
|
||||
return name
|
||||
except Exception:
|
||||
pass
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _read_resolv_conf_dns():
|
||||
try:
|
||||
with open("/etc/resolv.conf", encoding="utf-8") as f:
|
||||
return [line.split()[1] for line in f if line.strip().startswith("nameserver")]
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
def _read_network_state(interface, backend):
|
||||
"""Aktueller Ist-Zustand — IP/Prefix/Gateway aus _detect_interface_network
|
||||
(bereits für die DHCP-Server-Funktion gebaut), DNS aus resolv.conf,
|
||||
Modus (dhcp/static) backend-spezifisch ermittelt. Jeder Erkennungsschritt
|
||||
ist defensiv (try/except) — im Zweifel lieber "unbekannt" anzeigen als
|
||||
einen falschen Zustand zu behaupten."""
|
||||
net_info = _detect_interface_network(interface)
|
||||
mode = "unknown"
|
||||
if backend == "networkmanager":
|
||||
try:
|
||||
dev_out = subprocess.run(
|
||||
["nmcli", "-t", "-f", "GENERAL.CONNECTION", "device", "show", interface],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
conn_name = dev_out.stdout.split(":", 1)[1].strip() if ":" in dev_out.stdout else None
|
||||
if conn_name:
|
||||
method_out = subprocess.run(
|
||||
["nmcli", "-g", "ipv4.method", "connection", "show", conn_name],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
mode = "static" if method_out.stdout.strip() == "manual" else "dhcp"
|
||||
except Exception:
|
||||
pass
|
||||
elif backend == "dhcpcd":
|
||||
try:
|
||||
with open("/etc/dhcpcd.conf", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
block_match = re.search(rf"^interface\s+{re.escape(interface)}\s*$(.*?)(?=^interface\s|\Z)", content, re.S | re.M)
|
||||
block = block_match.group(1) if block_match else ""
|
||||
mode = "static" if re.search(r"^\s*static\s+ip_address=", block, re.M) else "dhcp"
|
||||
except OSError:
|
||||
mode = "dhcp"
|
||||
return {**net_info, "dns": _read_resolv_conf_dns(), "mode": mode, "backend": backend}
|
||||
|
||||
|
||||
def _backup_network_config(interface, backend):
|
||||
"""Kompletten Ist-Zustand vor einer Änderung sichern, damit
|
||||
_revert_network_config() ihn exakt wiederherstellen kann."""
|
||||
state = _read_network_state(interface, backend)
|
||||
backup = {
|
||||
"interface": interface, "backend": backend, "mode": state["mode"],
|
||||
"ip": state.get("ip"), "prefix": state.get("prefix"), "gateway": state.get("gateway"),
|
||||
"dns": state.get("dns", []),
|
||||
}
|
||||
if backend == "dhcpcd":
|
||||
try:
|
||||
with open("/etc/dhcpcd.conf", encoding="utf-8") as f:
|
||||
backup["dhcpcd_conf"] = f.read()
|
||||
except OSError:
|
||||
backup["dhcpcd_conf"] = None
|
||||
elif backend == "networkmanager":
|
||||
try:
|
||||
dev_out = subprocess.run(
|
||||
["nmcli", "-t", "-f", "GENERAL.CONNECTION", "device", "show", interface],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
backup["nm_connection"] = dev_out.stdout.split(":", 1)[1].strip() if ":" in dev_out.stdout else None
|
||||
except Exception:
|
||||
backup["nm_connection"] = None
|
||||
return backup
|
||||
|
||||
|
||||
def _apply_network_config(backend, interface, mode, ip, prefix, gateway, dns_list):
|
||||
"""Wendet die neue Konfiguration über das erkannte Backend an. Gibt
|
||||
(ok, message) zurück statt zu werfen, damit der Aufrufer immer eine
|
||||
Flash-Meldung bekommt statt eines 500ers."""
|
||||
dns_csv = ",".join(dns_list)
|
||||
if backend == "networkmanager":
|
||||
try:
|
||||
dev_out = subprocess.run(
|
||||
["nmcli", "-t", "-f", "GENERAL.CONNECTION", "device", "show", interface],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
conn_name = dev_out.stdout.split(":", 1)[1].strip() if ":" in dev_out.stdout else None
|
||||
if not conn_name:
|
||||
return False, f"Keine aktive NetworkManager-Verbindung für {interface} gefunden."
|
||||
args = ["nmcli", "connection", "modify", conn_name]
|
||||
if mode == "static":
|
||||
args += ["ipv4.method", "manual", "ipv4.addresses", f"{ip}/{prefix}", "ipv4.gateway", gateway]
|
||||
else:
|
||||
args += ["ipv4.method", "auto", "ipv4.addresses", "", "ipv4.gateway", ""]
|
||||
if dns_csv:
|
||||
args += ["ipv4.dns", dns_csv, "ipv4.ignore-auto-dns", "yes"]
|
||||
else:
|
||||
args += ["ipv4.dns", "", "ipv4.ignore-auto-dns", "no"]
|
||||
ok, out = _dhcp_run_privileged(args, timeout=15)
|
||||
if not ok:
|
||||
return False, out
|
||||
return _dhcp_run_privileged(["nmcli", "connection", "up", conn_name], timeout=20)
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
if backend == "dhcpcd":
|
||||
try:
|
||||
with open("/etc/dhcpcd.conf", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
content = re.sub(rf"^interface\s+{re.escape(interface)}\s*$(.*?)(?=^interface\s|\Z)", "", content, flags=re.S | re.M)
|
||||
block_lines = [f"interface {interface}"]
|
||||
if mode == "static":
|
||||
block_lines.append(f"static ip_address={ip}/{prefix}")
|
||||
if gateway:
|
||||
block_lines.append(f"static routers={gateway}")
|
||||
if dns_list:
|
||||
block_lines.append(f"static domain_name_servers={' '.join(dns_list)}")
|
||||
if len(block_lines) > 1:
|
||||
content = content.rstrip() + "\n\n" + "\n".join(block_lines) + "\n"
|
||||
with open("/etc/dhcpcd.conf", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
return _dhcp_run_privileged(["systemctl", "restart", "dhcpcd"], timeout=20)
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
return False, "Kein unterstütztes Netzwerk-Backend erkannt (weder NetworkManager noch dhcpcd aktiv)."
|
||||
|
||||
|
||||
def _revert_network_config(token):
|
||||
"""Timer-Callback: falls binnen NETWORK_REVERT_SECONDS keine Bestätigung
|
||||
einging, gesicherten Zustand wiederherstellen. Läuft in einem
|
||||
Hintergrund-Thread (threading.Timer) — Fehler werden geloggt statt die
|
||||
App zum Absturz zu bringen."""
|
||||
entry = _pending_network_revert.get(token)
|
||||
if not entry:
|
||||
return
|
||||
backup = entry["backup"]
|
||||
try:
|
||||
if backup["backend"] == "dhcpcd" and backup.get("dhcpcd_conf") is not None:
|
||||
with open("/etc/dhcpcd.conf", "w", encoding="utf-8") as f:
|
||||
f.write(backup["dhcpcd_conf"])
|
||||
subprocess.run(["systemctl", "restart", "dhcpcd"], timeout=20)
|
||||
elif backup["backend"] == "networkmanager":
|
||||
_apply_network_config(
|
||||
"networkmanager", backup["interface"],
|
||||
backup["mode"], backup.get("ip"), backup.get("prefix"), backup.get("gateway"), backup.get("dns", []),
|
||||
)
|
||||
log_action_system("settings.network_revert", backup["interface"], "automatisch nach Timeout zurückgerollt")
|
||||
except Exception as e:
|
||||
app.logger.error("Network auto-revert failed: %s", e)
|
||||
finally:
|
||||
_pending_network_revert.pop(token, None)
|
||||
|
||||
|
||||
@app.route("/settings", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def settings():
|
||||
@@ -1227,16 +1427,75 @@ def settings():
|
||||
if not current_user.has_permission("settings_system.edit"):
|
||||
flash("Keine Berechtigung, die Systemeinstellungen zu ändern.", "danger")
|
||||
return redirect(url_for("settings"))
|
||||
new_interval = int(request.form["interval"])
|
||||
set_setting("interval", new_interval)
|
||||
set_setting("check_interval", new_interval * 60)
|
||||
log_action("settings.update", "Prüfintervall", f"{new_interval} Minuten")
|
||||
_restart_check_service()
|
||||
|
||||
flash(f"Intervall auf {new_interval} Minuten gesetzt und Service neu gestartet!", "success")
|
||||
if "interval" in request.form:
|
||||
new_interval = int(request.form["interval"])
|
||||
set_setting("interval", new_interval)
|
||||
set_setting("check_interval", new_interval * 60)
|
||||
log_action("settings.update", "Prüfintervall", f"{new_interval} Minuten")
|
||||
_restart_check_service()
|
||||
flash(f"Intervall auf {new_interval} Minuten gesetzt und Service neu gestartet!", "success")
|
||||
|
||||
elif "apply_network" in request.form:
|
||||
interface = request.form.get("net_interface", "").strip()
|
||||
mode = request.form.get("net_mode", "dhcp")
|
||||
ip = request.form.get("net_ip", "").strip()
|
||||
prefix = request.form.get("net_prefix", "").strip()
|
||||
gateway = request.form.get("net_gateway", "").strip()
|
||||
dns_list = [d.strip() for d in request.form.get("net_dns", "").split(",") if d.strip()]
|
||||
backend = _detect_network_backend()
|
||||
|
||||
if interface not in _list_network_interfaces():
|
||||
flash(f"Interface „{interface}“ existiert nicht auf diesem Host.", "danger")
|
||||
elif backend == "unknown":
|
||||
flash("Kein unterstütztes Netzwerk-Backend erkannt — Änderungen über die App sind deaktiviert.", "danger")
|
||||
elif mode == "static" and not (ip and prefix and gateway):
|
||||
flash("Für eine statische Konfiguration werden IP-Adresse, Prefix und Gateway benötigt.", "danger")
|
||||
else:
|
||||
backup = _backup_network_config(interface, backend)
|
||||
ok, out = _apply_network_config(backend, interface, mode, ip, prefix, gateway, dns_list)
|
||||
if ok:
|
||||
token = secrets.token_hex(8)
|
||||
timer = threading.Timer(NETWORK_REVERT_SECONDS, _revert_network_config, args=(token,))
|
||||
timer.daemon = True
|
||||
_pending_network_revert.clear() # nur eine ausstehende Änderung gleichzeitig
|
||||
_pending_network_revert[token] = {"timer": timer, "backup": backup}
|
||||
timer.start()
|
||||
log_action("settings.network_apply", interface, f"Modus {mode}")
|
||||
flash(
|
||||
f"Netzwerkkonfiguration angewendet. Falls diese Seite jetzt noch erreichbar ist, bitte "
|
||||
f"unten bestätigen — sonst wird nach {NETWORK_REVERT_SECONDS}s automatisch zurückgerollt.",
|
||||
"success",
|
||||
)
|
||||
else:
|
||||
flash(f"Anwenden fehlgeschlagen: {out}", "danger")
|
||||
|
||||
elif "confirm_network" in request.form:
|
||||
token = request.form.get("confirm_network")
|
||||
entry = _pending_network_revert.pop(token, None)
|
||||
if entry:
|
||||
entry["timer"].cancel()
|
||||
log_action("settings.network_confirm", entry["backup"]["interface"])
|
||||
flash("Netzwerkkonfiguration bestätigt — kein automatisches Rollback mehr.", "success")
|
||||
else:
|
||||
flash("Keine ausstehende Bestätigung gefunden (evtl. bereits abgelaufen).", "danger")
|
||||
|
||||
return redirect(url_for("settings"))
|
||||
|
||||
return render_template("settings.html", interval=interval)
|
||||
net_backend = _detect_network_backend()
|
||||
net_interface = get_setting("net_interface") or (_list_network_interfaces() or [None])[0]
|
||||
net_state = _read_network_state(net_interface, net_backend) if net_interface else None
|
||||
pending_token = next(iter(_pending_network_revert), None)
|
||||
return render_template(
|
||||
"settings.html",
|
||||
interval=interval,
|
||||
net_backend=net_backend,
|
||||
net_interfaces=_list_network_interfaces(),
|
||||
net_interface=net_interface,
|
||||
net_state=net_state,
|
||||
net_revert_seconds=NETWORK_REVERT_SECONDS,
|
||||
pending_network_token=pending_token,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/settings/import-export")
|
||||
|
||||
@@ -1,34 +1,129 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "settings_system" %}
|
||||
{% block page_title %}Systemeinstellungen{% endblock %}
|
||||
{% block page_sub %}<div class="topbar-sub">Prüfintervall für das Monitoring</div>{% endblock %}
|
||||
{% block page_sub %}<div class="topbar-sub">Prüfintervall und Netzwerkkonfiguration dieses Hosts</div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card card-pad" style="max-width:420px;">
|
||||
<div class="section-head" style="margin-bottom:16px;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;">Prüfintervall</h2>
|
||||
<div class="hint">Wie oft sollen Geräte auf Erreichbarkeit geprüft werden?</div>
|
||||
<div class="settings-grid">
|
||||
|
||||
<div class="card card-pad">
|
||||
<div class="section-head" style="margin-bottom:16px;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;">Prüfintervall</h2>
|
||||
<div class="hint">Wie oft sollen Geräte auf Erreichbarkeit geprüft werden?</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if current_user.has_permission('settings_system.edit') %}
|
||||
<form method="post">
|
||||
{% if current_user.has_permission('settings_system.edit') %}
|
||||
<form method="post">
|
||||
<div class="field">
|
||||
<label for="interval">Intervall (Minuten)</label>
|
||||
<input type="number" name="interval" id="interval" value="{{ interval }}" min="1" required>
|
||||
<div class="field-hint">Der Hintergrund-Dienst (rpi-check.service) wird nach dem Speichern automatisch neu gestartet.</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
<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>
|
||||
Speichern & Service neustarten
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="field">
|
||||
<label for="interval">Intervall (Minuten)</label>
|
||||
<input type="number" name="interval" id="interval" value="{{ interval }}" min="1" required>
|
||||
<div class="field-hint">Der Hintergrund-Dienst (rpi-check.service) wird nach dem Speichern automatisch neu gestartet.</div>
|
||||
<label>Intervall (Minuten)</label>
|
||||
<input type="number" value="{{ interval }}" disabled>
|
||||
<div class="field-hint">Nur Lesezugriff — für Änderungen fehlt das Recht „Systemeinstellungen ändern“.</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
<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>
|
||||
Speichern & Service neustarten
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="field">
|
||||
<label>Intervall (Minuten)</label>
|
||||
<input type="number" value="{{ interval }}" disabled>
|
||||
<div class="field-hint">Nur Lesezugriff — für Änderungen fehlt das Recht „Systemeinstellungen ändern“.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card card-pad" style="grid-column:1 / -1;">
|
||||
<div class="section-head" style="margin-bottom:16px;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;">Netzwerkeinstellungen</h2>
|
||||
<div class="hint">IP-Adresse, DNS-Server und DHCP/Statisch-Umschaltung dieses Hosts selbst (nicht zu verwechseln mit dem DHCP-<em>Server</em> für Clients unter „DHCP“).</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2" style="align-items:center; margin-bottom:16px; flex-wrap:wrap;">
|
||||
{% if net_backend == 'unknown' %}
|
||||
<span class="pill unknown">Kein unterstütztes Backend erkannt</span>
|
||||
{% else %}
|
||||
<span class="pill online">{{ 'NetworkManager' if net_backend == 'networkmanager' else 'dhcpcd' }}</span>
|
||||
{% endif %}
|
||||
{% if net_state %}
|
||||
<span class="mono text-faint" style="font-size:12px;">
|
||||
{{ net_interface }}
|
||||
{% if net_state.ok %}— {{ net_state.ip }}/{{ net_state.prefix }}{% if net_state.gateway %}, Gateway {{ net_state.gateway }}{% endif %}{% endif %}
|
||||
</span>
|
||||
<span class="pill {{ 'user' if net_state.mode == 'static' else ('online' if net_state.mode == 'dhcp' else 'unknown') }}">
|
||||
{{ {'static': 'Statisch', 'dhcp': 'DHCP', 'unknown': 'Modus unbekannt'}[net_state.mode] }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if net_state and net_state.dns %}
|
||||
<div class="text-faint" style="font-size:12px; margin-bottom:16px;">Aktuelle DNS-Server: <span class="mono">{{ net_state.dns|join(', ') }}</span></div>
|
||||
{% endif %}
|
||||
|
||||
{% if pending_network_token %}
|
||||
<div class="card-pad" style="background:var(--warning-dim); border-radius:var(--radius-sm); margin-bottom:16px;">
|
||||
<p style="margin:0 0 12px; font-size:13px;">
|
||||
Neue Netzwerkkonfiguration wurde angewendet. Wenn diese Seite gerade noch lädt, funktioniert die Verbindung —
|
||||
bitte bestätigen, bevor automatisch zurückgerollt wird (nach {{ net_revert_seconds }}s ohne Bestätigung).
|
||||
</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="confirm_network" value="{{ pending_network_token }}">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<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>
|
||||
Verbindung funktioniert — bestätigen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif net_backend != 'unknown' and current_user.has_permission('settings_system.edit') %}
|
||||
<form method="post" data-confirm="Netzwerkkonfiguration wirklich ändern? Falls die Verbindung danach abbricht, wird die vorherige Konfiguration automatisch nach {{ net_revert_seconds }} Sekunden wiederhergestellt.">
|
||||
<input type="hidden" name="apply_network" value="1">
|
||||
<div class="field"><label>Interface</label>
|
||||
<select name="net_interface">
|
||||
{% for iface in net_interfaces %}
|
||||
<option value="{{ iface }}" {% if iface == net_interface %}selected{% endif %}>{{ iface }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="field"><label>Modus</label>
|
||||
<select name="net_mode" id="netModeSelect" onchange="document.getElementById('netStaticFields').classList.toggle('hidden', this.value !== 'static')">
|
||||
<option value="dhcp" {% if net_state.mode != 'static' %}selected{% endif %}>DHCP (automatisch)</option>
|
||||
<option value="static" {% if net_state.mode == 'static' %}selected{% endif %}>Statisch</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="netStaticFields" class="{{ 'hidden' if net_state.mode != 'static' }}">
|
||||
<div class="field"><label>IP-Adresse</label>
|
||||
<input type="text" name="net_ip" value="{{ net_state.ip if net_state.mode == 'static' else '' }}" placeholder="z.B. 192.168.1.50">
|
||||
</div>
|
||||
<div class="field"><label>Prefix (CIDR-Bits)</label>
|
||||
<input type="number" name="net_prefix" min="1" max="32" value="{{ net_state.prefix if net_state.mode == 'static' else '' }}" placeholder="z.B. 24">
|
||||
</div>
|
||||
<div class="field"><label>Gateway</label>
|
||||
<input type="text" name="net_gateway" value="{{ net_state.gateway if net_state.mode == 'static' else '' }}" placeholder="z.B. 192.168.1.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field"><label>DNS-Server</label>
|
||||
<input type="text" name="net_dns" value="{{ net_state.dns|join(', ') if net_state and net_state.dns else '' }}" placeholder="z.B. 1.1.1.1, 8.8.8.8">
|
||||
<div class="field-hint">Kommagetrennt. Leer lassen, um die per DHCP zugewiesenen DNS-Server zu verwenden.</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
<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>
|
||||
Netzwerkkonfiguration anwenden
|
||||
</button>
|
||||
<p class="text-faint" style="font-size:11px; margin-top:10px;">
|
||||
⚠ Kann die Erreichbarkeit dieses Hosts unterbrechen. Ohne Bestätigung wird automatisch nach {{ net_revert_seconds }}s zurückgerollt.
|
||||
</p>
|
||||
</form>
|
||||
{% elif net_backend == 'unknown' %}
|
||||
<p class="text-faint" style="font-size:12.5px;">
|
||||
Weder NetworkManager noch dhcpcd aktiv erkannt — Netzwerkänderungen über diese Seite sind deaktiviert.
|
||||
Bitte die Netzwerkkonfiguration dieses Hosts manuell vornehmen.
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="text-faint" style="font-size:12.5px;">Für Änderungen fehlt das Recht „Systemeinstellungen ändern“.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user