Logging-Infrastruktur (Logs können nach einem Import mit vielen Geräten schnell vollaufen): - Vier eigene, rotierte Log-Dateien statt allem in /var/log vermischt: Live (poe.sh, jetzt eine einzige durchgehend fortgeschriebene Datei statt vorher ein neues rpi-<timestamp>.log pro Dienst-Neustart), Änderungen (Spiegel der audit_log-Tabelle), App (Flask-Fehler/ Warnungen, vorher nur im journalctl-Output), Kea-DHCP (eigene Logging-Konfiguration in der generierten kea-dhcp4.conf) - Kea-DHCP-Log bewusst NICHT im gemeinsamen Verzeichnis: Keas AppArmor-Profil erlaubt Schreibzugriff exklusiv auf /var/log/kea/kea-dhcp4.log (kein Verzeichnis-Wildcard) -- live als "DENIED ... mknod ... .lock" reproduziert; die App nutzt Keas eigenen, bereits erlaubten Pfad statt das AppArmor-Profil eines Systemdienstes aufzuweichen. logrotate deckt trotzdem beide Verzeichnisse mit einer Regel ab - Neue Log-Seite "Kea-DHCP" (Logs-Bereich, eigenes Recht logs_kea.view) - Rotation (Intervall täglich/wöchentlich/monatlich, Aufbewahrung als Anzahl Rotationen) unter Systemeinstellungen → Logs einstellbar, generiert /etc/logrotate.d/poe-manager; copytruncate, da keiner der drei schreibenden Prozesse ein Reopen-Signal implementiert - Einmalige Migration räumt alte, verwaiste rpi-*.log-Dateien auf Zeitzone (Systemeinstellungen, mit Hostname in einer Kachel "Host"): setzt die System-Zeitzone per timedatectl, damit Logs/Änderungsverlauf die tatsächliche lokale Zeit statt UTC zeigen. time.tzset() direkt nach dem Setzen lässt die bereits laufende poe_web.service-Instanz die neue Zeitzone sofort übernehmen, ohne Neustart. Systemeinstellungen neu sortiert: Host + Netzwerkeinstellungen (beide Host-Konfiguration) nebeneinander, dann Logs, dann Prüfintervall. Netzwerkeinstellungen: DNS-Anzeige zeigte bisher den lokalen systemd-resolved-Stub (127.0.0.53) aus /etc/resolv.conf statt der echten Server -- jetzt über "resolvectl dns <interface>", inkl. Fix für eine falsch geparste IPv6-Fortsetzungszeile ohne Label, und gefiltert auf IPv4 (diese Seite verwaltet nur IPv4-Konfiguration, per IPv6-Router-Advertisement gelernte Server sind hier irreführend). Dashboard: die vier Stat-Kacheln (Online/Offline/Deaktiviert/Gesamt) sind jetzt klickbare Filter (blenden alle anderen Abschnitte aus, Klick auf die aktive Kachel oder "Gesamt" hebt den Filter auf); jeder Abschnitt lässt sich unabhängig davon per Klick auf seinen Titel ein-/ ausklappen. Beide Zustände bleiben über die automatischen Live-Updates hinweg erhalten. Design-Politur: - Hauptinhaltsbereich nutzt jetzt die volle verfügbare Breite (kein Bootstrap-typisches festes max-width mehr) -- Modals bleiben bewusst bei ihrer kompakten, festen Breite Akzentfarbe exakt auf das Orange aus dem WiS-Logo abgestimmt (#ff7100, per Pixel-Sampling ermittelt) statt einer nur ähnlichen, frei gewählten Nuance - Abmelden führt zurück auf das (auch ohne Login einsehbare) Dashboard statt auf die Login-Seite Live gegen die echte Test-VM verifiziert: alle vier Logs werden geschrieben, logrotate-Konfiguration korrekt generiert, ein Zeitzonenwechsel (UTC → Europe/Berlin) wirkte sofort, DNS-Anzeige zeigt jetzt die echten IPv4-Server (1.1.1.1, 8.8.8.8) statt Stub/IPv6, Dashboard-Filter/-Collapse und Logout-Redirect per Playwright bestätigt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
222 lines
11 KiB
HTML
222 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
{% set active_page = "settings_system" %}
|
|
{% block page_title %}Systemeinstellungen{% endblock %}
|
|
{% block page_sub %}<div class="topbar-sub">Prüfintervall und Netzwerkkonfiguration dieses Hosts</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;">Host</h2>
|
|
<div class="hint">Name und Zeitzone dieses Hosts.</div>
|
|
</div>
|
|
</div>
|
|
{% if current_user.has_permission('settings_system.edit') %}
|
|
<form method="post">
|
|
<div class="field">
|
|
<label for="hostname">Hostname</label>
|
|
<input type="text" name="hostname" id="hostname" value="{{ current_hostname or '' }}" pattern="[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?" required>
|
|
</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>
|
|
Hostname setzen
|
|
</button>
|
|
</form>
|
|
<hr style="border:none; border-top:1px solid var(--border-soft); margin:18px 0;">
|
|
<form method="post">
|
|
<div class="field">
|
|
<label for="timezone">Zeitzone</label>
|
|
<select name="timezone" id="timezone" required>
|
|
{% for tz in timezones %}
|
|
<option value="{{ tz }}" {% if tz == current_timezone %}selected{% endif %}>{{ tz }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="field-hint">Bestimmt die lokale Zeit in allen Logs und im Änderungsverlauf — wirkt sofort für diese laufende App-Instanz, ohne Dienst-Neustart.</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>
|
|
Zeitzone setzen
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="field">
|
|
<label>Hostname</label>
|
|
<input type="text" value="{{ current_hostname or '' }}" disabled>
|
|
</div>
|
|
<div class="field">
|
|
<label>Zeitzone</label>
|
|
<input type="text" value="{{ current_timezone or '' }}" disabled>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="card card-pad">
|
|
<div class="section-head" style="margin-bottom:16px;">
|
|
<div>
|
|
<h2 style="font-size:16px;">Netzwerkeinstellungen</h2>
|
|
<div class="hint">IP/DNS/DHCP-Umschaltung dieses Hosts (nicht der DHCP-<em>Server</em> 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': 'NetworkManager', 'dhcpcd': 'dhcpcd', 'netplan': 'netplan'}[net_backend] }}</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, dhcpcd noch netplan/systemd-networkd 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 class="card card-pad">
|
|
<div class="section-head" style="margin-bottom:16px;">
|
|
<div>
|
|
<h2 style="font-size:16px;">Logs</h2>
|
|
<div class="hint">Rotation & Aufbewahrung von Live-, Änderungs-, App- und Kea-DHCP-Log.</div>
|
|
</div>
|
|
</div>
|
|
{% if current_user.has_permission('settings_system.edit') %}
|
|
<form method="post">
|
|
<input type="hidden" name="save_log_rotation" value="1">
|
|
<div class="field">
|
|
<label for="log_rotation_interval">Rotations-Intervall</label>
|
|
<select name="log_rotation_interval" id="log_rotation_interval">
|
|
<option value="daily" {% if log_rotation_interval == "daily" %}selected{% endif %}>Täglich</option>
|
|
<option value="weekly" {% if log_rotation_interval == "weekly" %}selected{% endif %}>Wöchentlich</option>
|
|
<option value="monthly" {% if log_rotation_interval == "monthly" %}selected{% endif %}>Monatlich</option>
|
|
</select>
|
|
</div>
|
|
<div class="field">
|
|
<label for="log_rotation_keep">Aufbewahrung (Anzahl Rotationen)</label>
|
|
<input type="number" name="log_rotation_keep" id="log_rotation_keep" value="{{ log_rotation_keep }}" min="1" required>
|
|
<div class="field-hint">Standard: wöchentlich, 4 Rotationen (≈ 1 Monat Historie je Log).</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
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="field">
|
|
<label>Rotations-Intervall</label>
|
|
<input type="text" value="{{ log_rotation_interval }}" disabled>
|
|
</div>
|
|
<div class="field">
|
|
<label>Aufbewahrung (Anzahl Rotationen)</label>
|
|
<input type="text" value="{{ log_rotation_keep }}" disabled>
|
|
</div>
|
|
{% endif %}
|
|
<div class="field-hint mono" style="margin-top:10px; font-size:11px; line-height:1.6;">
|
|
Live: {{ log_paths.live }}<br>
|
|
Änderungen: {{ log_paths.changes }}<br>
|
|
App: {{ log_paths.app }}<br>
|
|
Kea-DHCP: {{ log_paths.kea }}
|
|
</div>
|
|
</div>
|
|
|
|
<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>
|
|
{% 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>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>
|
|
{% endif %}
|
|
</div>
|
|
|
|
</div>
|
|
{% endblock %}
|