Neu: DHCP-Reservierungen aus Client-Stammdaten (Systemeinstellungen -> DHCP)
- Neue Unterseite unter Einstellungen (eigenes Rechtepaar
settings_dhcp.view/settings_dhcp.edit, in PERMISSIONS/NAV_ITEMS/
_nav_key_visible integriert, Kill-Switch über settings_group.view
greift wie bei den anderen Einstellungen-Unterpunkten).
- Rein lesende Installations-/Status-Erkennung (shutil.which("dhcpd"),
systemctl is-active isc-dhcp-server) — die App installiert/startet nie
selbst einen DHCP-Dienst, sondern zeigt bei fehlender Installation den
passenden manuellen Befehl an.
- Konfigurierbare Netzwerkparameter (Interface/Subnet/Netzmaske/Range/
DNS/Domain/Lease-Zeiten/Ausgabepfad), gespeichert als dhcp_*-Schlüssel
in der bestehenden settings-Tabelle.
- Reservierungen werden aus aktiven Geräten mit gültiger MAC+IP generiert
(Hostname aus Gerätename abgeleitet, Kollisionen automatisch
durchnummeriert, Geräte ohne MAC/IP werden übersprungen).
- "In Datei schreiben" (nur mit settings_dhcp.edit) schreibt eine
separate Include-Datei statt der aktiven dhcpd.conf; kein automatischer
Dienst-Reload/-Restart durch die App.
- Live getestet: Status-Erkennung, Config speichern, Reservierungs-
Generierung inkl. Namenskollisionen, Datei-Schreiben in sicheren
Testpfad, View-only-Gating (Formular ausgeblendet, POST blockiert),
Kill-Switch über settings_group.view.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "settings_dhcp" %}
|
||||
{% set can_edit = current_user.has_permission('settings_dhcp.edit') %}
|
||||
{% block page_title %}DHCP{% endblock %}
|
||||
{% block page_sub %}<div class="topbar-sub">Reservierungen aus den Client-Stammdaten generieren</div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="settings-grid">
|
||||
|
||||
<div class="card card-pad">
|
||||
<div class="section-head" style="margin-bottom:16px;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;">DHCP-Server-Status</h2>
|
||||
<div class="hint">Rein lesende Erkennung — die App installiert oder startet nie selbst einen DHCP-Dienst.</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if status.installed %}
|
||||
<div class="flex gap-2" style="align-items:center; margin-bottom:10px;">
|
||||
<span class="pill online">Installiert</span>
|
||||
{% if status.active is sameas true %}
|
||||
<span class="pill online">Dienst aktiv</span>
|
||||
{% elif status.active is sameas false %}
|
||||
<span class="pill offline">Dienst inaktiv</span>
|
||||
{% else %}
|
||||
<span class="pill unknown">Dienst-Status unbekannt</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if status.version %}<div class="text-faint mono" style="font-size:12px;">{{ status.version }}</div>{% endif %}
|
||||
{% if status.active is not sameas true %}
|
||||
<p class="text-faint" style="font-size:11.5px; margin-top:10px;">
|
||||
Dienst manuell starten: <code>sudo systemctl enable --now isc-dhcp-server</code>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="pill disabled">Nicht installiert</span>
|
||||
<p class="text-faint" style="font-size:11.5px; margin-top:10px;">
|
||||
Kein <code>dhcpd</code> auf diesem Host gefunden. Installation bewusst manuell, außerhalb der App:<br>
|
||||
<code>sudo apt-get install isc-dhcp-server</code>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card card-pad">
|
||||
<div class="section-head" style="margin-bottom:16px;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;">Netzwerk-Konfiguration</h2>
|
||||
<div class="hint">Grundlage für die generierte Subnet- und Reservierungs-Deklaration.</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if can_edit %}
|
||||
<form method="post">
|
||||
<input type="hidden" name="save_dhcp_config" value="1">
|
||||
<div class="field"><label>Interface</label>
|
||||
<input type="text" name="dhcp_interface" value="{{ cfg.dhcp_interface }}">
|
||||
</div>
|
||||
<div class="field"><label>Subnet</label>
|
||||
<input type="text" name="dhcp_subnet" value="{{ cfg.dhcp_subnet }}">
|
||||
</div>
|
||||
<div class="field"><label>Netzmaske</label>
|
||||
<input type="text" name="dhcp_netmask" value="{{ cfg.dhcp_netmask }}">
|
||||
</div>
|
||||
<div class="field"><label>Range Start</label>
|
||||
<input type="text" name="dhcp_range_start" value="{{ cfg.dhcp_range_start }}">
|
||||
</div>
|
||||
<div class="field"><label>Range Ende</label>
|
||||
<input type="text" name="dhcp_range_end" value="{{ cfg.dhcp_range_end }}">
|
||||
</div>
|
||||
<div class="field"><label>DNS-Server</label>
|
||||
<input type="text" name="dhcp_dns" value="{{ cfg.dhcp_dns }}">
|
||||
<div class="field-hint">Kommagetrennt, falls mehrere.</div>
|
||||
</div>
|
||||
<div class="field"><label>Domain</label>
|
||||
<input type="text" name="dhcp_domain" value="{{ cfg.dhcp_domain }}">
|
||||
</div>
|
||||
<div class="field"><label>Lease-Zeit Standard (Sek.)</label>
|
||||
<input type="number" name="dhcp_lease_default" value="{{ cfg.dhcp_lease_default }}">
|
||||
</div>
|
||||
<div class="field"><label>Lease-Zeit Maximum (Sek.)</label>
|
||||
<input type="number" name="dhcp_lease_max" value="{{ cfg.dhcp_lease_max }}">
|
||||
</div>
|
||||
<div class="field"><label>Ausgabepfad (Reservierungsdatei)</label>
|
||||
<input type="text" name="dhcp_output_path" value="{{ cfg.dhcp_output_path }}" class="mono">
|
||||
<div class="field-hint">Separate Include-Datei — nicht die aktive dhcpd.conf. Per <code>include "{{ cfg.dhcp_output_path }}";</code> einmalig manuell einbinden.</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>
|
||||
Konfiguration speichern
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="text-faint" style="font-size:12.5px;">Für Änderungen fehlt das Recht „DHCP ändern“.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card card-pad" style="grid-column:1 / -1;">
|
||||
<div class="section-head" style="margin-bottom:16px;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;">Reservierungen aus den Clients</h2>
|
||||
<div class="hint">
|
||||
{{ reservations|length }} aktive{{ ' Clients' if reservations|length != 1 else 'r Client' }} mit gültiger MAC + IP.
|
||||
{% if skipped_count %}{{ skipped_count }} aktive{{ ' Clients' if skipped_count != 1 else 'r Client' }} ohne MAC/IP übersprungen.{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if can_edit %}
|
||||
<form method="post" data-confirm="Reservierungsdatei nach „{{ cfg.dhcp_output_path }}“ schreiben? Der DHCP-Dienst übernimmt die Änderung erst nach einem manuellen Reload.">
|
||||
<input type="hidden" name="write_dhcp_file" value="1">
|
||||
<button type="submit" class="btn btn-secondary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>
|
||||
In Datei schreiben
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if reservations %}
|
||||
<div class="table-wrap" style="margin-bottom:18px;">
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>Hostname</th><th>MAC</th><th>IP-Adresse</th><th>Client-Name</th></tr></thead>
|
||||
<tbody>
|
||||
{% for r in reservations %}
|
||||
<tr>
|
||||
<td class="mono">{{ r.hostname }}</td>
|
||||
<td class="mono">{{ r.mac }}</td>
|
||||
<td class="mono">{{ r.ip }}</td>
|
||||
<td>{{ r.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-faint" style="font-size:12.5px; margin-bottom:18px;">Keine aktiven Clients mit MAC + IP vorhanden.</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="permission-group-title">Generierte Konfiguration (Vorschau)</div>
|
||||
<pre class="code-preview">{{ preview }}</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user