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.
511 lines
24 KiB
HTML
511 lines
24 KiB
HTML
{% 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 und eigene Options aus den Client-Stammdaten (Kea DHCP)</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">Installation/Dienststeuerung nur über die Buttons unten.</div>
|
||
</div>
|
||
</div>
|
||
{% if status.installed %}
|
||
<div class="flex gap-2" style="align-items:center; margin-bottom:10px; flex-wrap:wrap;">
|
||
<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 %}
|
||
{% if status.enabled is sameas true %}<span class="pill user">Autostart an</span>{% endif %}
|
||
</div>
|
||
{% if status.version %}<div class="text-faint mono" style="font-size:12px; margin-bottom:12px;">{{ status.version }}</div>{% endif %}
|
||
{% if can_edit %}
|
||
<div class="flex gap-2" style="flex-wrap:wrap;">
|
||
<form method="post" data-confirm="{{ dhcp_service }} aktivieren und (neu) starten? Übernimmt die zuletzt geschriebene Konfiguration.">
|
||
<input type="hidden" name="dhcp_service_action" value="enable_restart">
|
||
<button type="submit" class="btn btn-secondary btn-sm">Aktivieren & (neu) starten</button>
|
||
</form>
|
||
{% if status.active is sameas true %}
|
||
<form method="post" data-confirm="{{ dhcp_service }} stoppen und deaktivieren? Startet nach einem Host-Neustart dann nicht automatisch wieder.">
|
||
<input type="hidden" name="dhcp_service_action" value="stop">
|
||
<button type="submit" class="btn btn-secondary btn-sm" style="color:var(--danger);">Stoppen</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
{% else %}
|
||
<span class="pill disabled">Nicht installiert</span>
|
||
<p class="text-faint" style="font-size:11.5px; margin:10px 0;">
|
||
Kein <code>kea-dhcp4</code> auf diesem Host gefunden.
|
||
</p>
|
||
{% if can_edit %}
|
||
<form method="post" data-confirm="{{ dhcp_package }} jetzt installieren (apt-get)? Startet den Dienst noch nicht.">
|
||
<input type="hidden" name="install_dhcp_package" value="1">
|
||
<button type="submit" class="btn btn-primary btn-sm">{{ dhcp_package }} installieren</button>
|
||
</form>
|
||
{% endif %}
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="card card-pad">
|
||
<div class="section-head" style="margin-bottom:16px;">
|
||
<div>
|
||
<h2 style="font-size:16px;">Globale DHCP-Einstellungen</h2>
|
||
<div class="hint">Gelten für den gesamten Dienst, unabhängig von den Subnetzen unten.</div>
|
||
</div>
|
||
</div>
|
||
{% if can_edit %}
|
||
<form method="post">
|
||
<input type="hidden" name="save_dhcp_config" value="1">
|
||
<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 (Kea-Konfigurationsdatei)</label>
|
||
<input type="text" name="dhcp_output_path" value="{{ cfg.dhcp_output_path }}" class="mono">
|
||
<div class="field-hint">Standardmäßig die aktive Kea-Konfiguration. Nach dem Schreiben den Dienst per Button neu starten, damit die Änderung wirksam wird.</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;">Subnetze</h2>
|
||
<div class="hint">
|
||
Ein Host kann mehrere IPs/Interfaces mit jeweils eigenem Netz bedienen — je Subnetz eigene Range,
|
||
optional eigenes Gateway/DNS. Subnet/Netzmaske werden live vom Interface übernommen, nicht manuell gepflegt.
|
||
</div>
|
||
</div>
|
||
{% if can_edit %}
|
||
<button type="button" class="btn btn-primary" data-open-modal="addSubnetModal">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
||
Subnetz hinzufügen
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if subnets %}
|
||
<div class="table-wrap">
|
||
<div style="overflow-x:auto;">
|
||
<table class="data-table">
|
||
<thead><tr><th>Interface</th><th>Range</th><th>Gateway</th><th>DNS</th><th>Erkanntes Netz</th><th style="width:1%;">Aktionen</th></tr></thead>
|
||
<tbody>
|
||
{% for s in subnets %}
|
||
{% set st = subnet_status.get(s.id) %}
|
||
<tr {% if not s.enabled %}style="opacity:.55;"{% endif %}>
|
||
<td class="mono">{{ s.interface }}</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.dns or '—' }}</td>
|
||
<td>
|
||
{% if not s.enabled %}
|
||
<span class="pill disabled">Deaktiviert</span>
|
||
{% elif st and st.ok %}
|
||
<span class="pill online">Erkannt</span>
|
||
<span class="mono text-faint" style="font-size:11.5px;">{{ st.network }}</span>
|
||
{% else %}
|
||
<span class="pill offline">Nicht erkannt</span>
|
||
{% if st %}<span class="text-faint" style="font-size:11px;">{{ st.error }}</span>{% endif %}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if can_edit %}
|
||
<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 }}">
|
||
<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>
|
||
<form method="post" data-confirm="Subnetz {{ s.range_start }}–{{ s.range_end }} löschen?">
|
||
<input type="hidden" name="delete_dhcp_subnet" value="{{ s.id }}">
|
||
<button type="submit" class="icon-btn" style="color:var(--danger);" title="Löschen">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0l-1 14a2 2 0 01-2 2H7a2 2 0 01-2-2L4 6"/></svg>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<p class="text-faint" style="font-size:12.5px;">Noch kein Subnetz konfiguriert — der Dienst kann so nicht gestartet werden.</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;">DHCP-Options</h2>
|
||
<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>
|
||
{% if can_edit %}
|
||
<button type="button" class="btn btn-primary" data-open-modal="addOptionModal">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
||
Neue Option
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if option_defs %}
|
||
<div class="table-wrap">
|
||
<div style="overflow-x:auto;">
|
||
<table class="data-table">
|
||
<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>
|
||
{% for d in option_defs %}
|
||
<tr>
|
||
<td class="mono">{{ d.code }}</td>
|
||
<td class="mono">{{ d.name }}</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>
|
||
{% if can_edit %}
|
||
<form method="post" class="flex gap-2">
|
||
<input type="hidden" name="save_dhcp_option_global" value="1">
|
||
<input type="hidden" name="option_def_id" value="{{ d.id }}">
|
||
<input type="text" name="value" value="{{ option_values.get(d.id, {}).get('', '') }}" style="max-width:220px;" placeholder="(nicht gesetzt)">
|
||
<button type="submit" class="btn btn-secondary btn-sm">Speichern</button>
|
||
</form>
|
||
{% else %}
|
||
{{ option_values.get(d.id, {}).get('', '—') }}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if can_edit %}
|
||
<form method="post" data-confirm="Option „{{ d.name }}“ inkl. aller Werte/Overrides löschen?">
|
||
<input type="hidden" name="delete_dhcp_option" value="{{ d.id }}">
|
||
<button type="submit" class="icon-btn" style="color:var(--danger);" title="Löschen">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0l-1 14a2 2 0 01-2 2H7a2 2 0 01-2-2L4 6"/></svg>
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<p class="text-faint" style="font-size:12.5px;">Noch keine eigenen Options definiert.</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;">Aktive Leases</h2>
|
||
<div class="hint">
|
||
Direkt aus Kea gelesen (nicht aus den Reservierungen) — zeigt auch Clients OHNE eigene Reservierung,
|
||
die sich einfach eine freie IP aus dem Pool genommen haben.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if leases is none %}
|
||
<p class="text-faint" style="font-size:12.5px;">
|
||
Keine Lease-Datei gefunden — der Dienst wurde vermutlich noch nie gestartet oder hat noch keine Adresse vergeben.
|
||
</p>
|
||
{% elif leases %}
|
||
<div class="table-wrap">
|
||
<div style="overflow-x:auto;">
|
||
<table class="data-table">
|
||
<thead><tr><th>IP-Adresse</th><th>MAC</th><th>Hostname</th><th>Läuft ab</th><th>Status</th></tr></thead>
|
||
<tbody>
|
||
{% for l in leases %}
|
||
<tr>
|
||
<td class="mono">{{ l.ip }}</td>
|
||
<td class="mono">{{ l.mac }}</td>
|
||
<td>{{ l.hostname or '—' }}</td>
|
||
<td class="text-faint mono" style="font-size:12px;">{{ l.expires_at or '—' }}</td>
|
||
<td>
|
||
{% if l.reserved %}
|
||
<span class="pill online">Reserviert</span>
|
||
{% else %}
|
||
<span class="pill unknown" title="Client hat sich diese IP selbst aus dem Pool geholt, ohne eigene Reservierung">
|
||
⚠ Ohne Reservierung
|
||
</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<p class="text-faint" style="font-size:12.5px;">Aktuell keine aktiven Leases vergeben.</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 in einem der konfigurierten Subnetze.
|
||
{% if skipped_count %}{{ skipped_count }} aktive{{ ' Clients' if skipped_count != 1 else 'r Client' }} ohne MAC/IP übersprungen.{% endif %}
|
||
{% if out_of_subnet_count %}{{ out_of_subnet_count }} aktive{{ ' Clients außerhalb' if out_of_subnet_count != 1 else 'r Client außerhalb' }} aller konfigurierten Subnetze übersprungen.{% endif %}
|
||
</div>
|
||
</div>
|
||
{% if can_edit %}
|
||
<form method="post" data-confirm="Konfiguration nach „{{ cfg.dhcp_output_path }}“ schreiben? Der Dienst übernimmt die Änderung erst nach einem Neustart.">
|
||
<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><th>Subnetz</th><th>Optionen</th><th style="width:1%;">Aktionen</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>
|
||
<td class="mono text-faint" style="font-size:12px;">{{ r.subnet_label }}</td>
|
||
<td style="font-size:12px;">
|
||
{% for d in option_defs %}
|
||
{% set override = option_values.get(d.id, {}).get(r.mac) %}
|
||
{% set global_val = option_values.get(d.id, {}).get('') %}
|
||
{% if override %}
|
||
<span class="pill user" title="{{ d.name }} = {{ override }}">{{ d.name }} (eigen)</span>
|
||
{% elif global_val %}
|
||
<span class="pill" style="background:var(--muted-dim); color:var(--text-faint);" title="{{ d.name }} = {{ global_val }}">{{ d.name }} (global)</span>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</td>
|
||
<td>
|
||
{% if option_defs and can_edit %}
|
||
<button class="icon-btn" title="DHCP-Optionen für diesen Client" data-open-modal="deviceOptionsModal{{ loop.index }}">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h9M17 6h3M4 12h3M11 12h9M4 18h13M20 18h0"/><circle cx="15" cy="6" r="2"/><circle cx="9" cy="12" r="2"/><circle cx="17" cy="18" r="2"/></svg>
|
||
</button>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<!-- Modals außerhalb der Tabelle: ein <div> direkt in <tbody> ist
|
||
ungültiges HTML und wird vom Browser aus der Tabelle heraus
|
||
"foster-parented" — dabei kann die Eltern-Kind-Beziehung zwischen
|
||
Formular und Feldern zerrissen werden. -->
|
||
{% if option_defs and can_edit %}
|
||
{% for r in reservations %}
|
||
<div class="modal-overlay" id="deviceOptionsModal{{ loop.index }}">
|
||
<div class="modal">
|
||
<form method="post">
|
||
<input type="hidden" name="save_dhcp_device_options" value="1">
|
||
<input type="hidden" name="mac" value="{{ r.mac }}">
|
||
<div class="modal-header">
|
||
<h3>DHCP-Optionen — {{ r.name }}</h3>
|
||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="text-faint" style="font-size:11.5px; margin:0 0 14px;">Leer lassen, um den globalen Wert zu übernehmen.</p>
|
||
{% for d in option_defs %}
|
||
<div class="field">
|
||
<label>{{ d.name }} <span class="text-faint">(Code {{ d.code }})</span></label>
|
||
<input type="text" name="opt_{{ d.id }}" value="{{ option_values.get(d.id, {}).get(r.mac, '') }}"
|
||
placeholder="{{ option_values.get(d.id, {}).get('', '(kein globaler Wert)') }}">
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% endif %}
|
||
{% 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, Kea-JSON)</div>
|
||
<pre class="code-preview">{{ preview }}</pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{% if can_edit %}
|
||
<div class="modal-overlay" id="addOptionModal">
|
||
<div class="modal" style="max-width:1000px;">
|
||
<form method="post">
|
||
<input type="hidden" name="add_dhcp_option" value="1">
|
||
<div class="modal-header">
|
||
<h3>Neue DHCP-Option</h3>
|
||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="field"><label>Code</label>
|
||
<input type="number" name="code" min="1" max="254" required placeholder="z.B. 225">
|
||
</div>
|
||
<div class="field"><label>Name</label>
|
||
<input type="text" name="name" required placeholder="z.B. terminal-url" pattern="[a-z][a-z0-9-]*">
|
||
<div class="field-hint">Nur Kleinbuchstaben, Ziffern und Bindestrich, muss mit einem Buchstaben beginnen.</div>
|
||
</div>
|
||
<div class="field"><label>Datentyp</label>
|
||
<select name="type">
|
||
{% for key, label in option_types %}
|
||
<option value="{{ key }}">{{ label }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="field"><label>Beschreibung</label>
|
||
<input type="text" name="description" placeholder="z.B. Terminal-Boot-URL für Thin Clients">
|
||
</div>
|
||
<div class="field"><label>Globaler Wert (optional)</label>
|
||
<input type="text" name="initial_value" placeholder="z.B. http://bde/webmmi?M=SGM11">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Anlegen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="addSubnetModal">
|
||
<div class="modal">
|
||
<form method="post">
|
||
<input type="hidden" name="add_dhcp_subnet" value="1">
|
||
<div class="modal-header">
|
||
<h3>Subnetz hinzufügen</h3>
|
||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="field"><label>Interface</label>
|
||
<select name="interface" required>
|
||
{% for iface in interfaces %}
|
||
<option value="{{ iface }}">{{ iface }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="field"><label>Range Start</label>
|
||
<input type="text" name="range_start" required placeholder="z.B. 192.168.1.100">
|
||
</div>
|
||
<div class="field"><label>Range Ende</label>
|
||
<input type="text" name="range_end" required placeholder="z.B. 192.168.1.200">
|
||
<div class="field-hint">Muss im tatsächlich am Interface erkannten Netz liegen — sonst wird das Subnetz abgelehnt.</div>
|
||
</div>
|
||
<div class="field"><label>Gateway (optional)</label>
|
||
<input type="text" name="gateway" placeholder="Automatisch: erkanntes Gateway des Interfaces">
|
||
</div>
|
||
<div class="field"><label>DNS-Server (optional)</label>
|
||
<input type="text" name="dns" placeholder="z.B. 1.1.1.1, 8.8.8.8">
|
||
<div class="field-hint">Kommagetrennt, falls mehrere.</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Hinzufügen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Bearbeiten-Modals außerhalb der Karte/Tabelle (siehe Kommentar oben
|
||
bei den Reservierungen — dieselbe HTML-Validität-Begründung). -->
|
||
{% if can_edit %}
|
||
{% for s in subnets %}
|
||
<div class="modal-overlay" id="editSubnetModal{{ loop.index }}">
|
||
<div class="modal">
|
||
<form method="post">
|
||
<input type="hidden" name="edit_dhcp_subnet" value="{{ s.id }}">
|
||
<div class="modal-header">
|
||
<h3>Subnetz bearbeiten</h3>
|
||
<button type="button" class="modal-close" data-close-modal>×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="field"><label>Interface</label>
|
||
<select name="interface" required>
|
||
{% for iface in interfaces %}
|
||
<option value="{{ iface }}" {% if iface == s.interface %}selected{% endif %}>{{ iface }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="field"><label>Range Start</label>
|
||
<input type="text" name="range_start" value="{{ s.range_start }}" required>
|
||
</div>
|
||
<div class="field"><label>Range Ende</label>
|
||
<input type="text" name="range_end" value="{{ s.range_end }}" required>
|
||
<div class="field-hint">Muss im tatsächlich am Interface erkannten Netz liegen — sonst wird das Subnetz abgelehnt.</div>
|
||
</div>
|
||
<div class="field"><label>Gateway (optional)</label>
|
||
<input type="text" name="gateway" value="{{ s.gateway or '' }}" placeholder="Automatisch: erkanntes Gateway des Interfaces">
|
||
</div>
|
||
<div class="field"><label>DNS-Server (optional)</label>
|
||
<input type="text" name="dns" value="{{ s.dns or '' }}" placeholder="z.B. 1.1.1.1, 8.8.8.8">
|
||
<div class="field-hint">Kommagetrennt, falls mehrere.</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% endif %}
|
||
|
||
{% endblock %}
|