Kompletter Umbau des Permission-Systems weg von groben is_admin-Gates hin zu
einem zweistufigen, an die Sidebar-Struktur gespiegelten Rechtebaum:
Geräte (devices_group.view)
├─ Clients Lesen/Schreiben/Ändern/Löschen (+ PoE-Neustart)
├─ Switche Lesen/Schreiben/Ändern/Löschen
└─ Zugangsdaten Lesen/Schreiben/Ändern/Löschen (jetzt eigene Rechte,
vorher an switches.* gekoppelt)
Logs (logs_group.view)
├─ Live Lesen
└─ Änderungen Lesen
Einstellungen (settings_group.view)
├─ Benutzer Lesen/Schreiben/Ändern/Löschen
├─ Gruppen Lesen/Schreiben/Ändern/Löschen
├─ Systemeinstellungen Lesen/Ändern
└─ Im-/Export Lesen (Export)/Ändern (Import)
- User.has_permission() ist jetzt hierarchisch: das "Bereich anzeigen"-Recht
einer Top-Level-Gruppe wirkt als Kill-Switch für alle Kind-Rechte
darunter, auch wenn ein Kind-Recht einzeln noch gesetzt ist. Mit
Testgruppe verifiziert (devices.view ohne devices_group.view -> /devices
liefert 302, "Geräte" verschwindet komplett aus der Sidebar; nach
Zurücksetzen sofort wieder 200).
- devices.toggle entfällt, ist jetzt Teil von devices.edit (Ändern).
- Neue eigenständige credentials.*-Rechte statt Kopplung an switches.*.
- Benutzer- und Gruppenverwaltung sind jetzt ebenfalls granular/delegierbar
(users.*/groups.*) statt fest is_admin-exklusiv — dafür neue,
fest einprogrammierte Eskalationsschranken: Admin-Konten anlegen/ändern/
löschen sowie Admin-Zuweisung bleiben unabhängig von delegierten Rechten
echten Admins vorbehalten (mit Testgruppe verifiziert: Anlegen als Admin,
Bearbeiten/Löschen bestehender Admin-Konten und Zuweisen zu "admin"
wurden alle korrekt blockiert, normale Benutzerverwaltung funktioniert).
- "Admin" (virtuell) und "Benutzer" (Standardgruppe, neues is_system-Flag)
sind jetzt echte Systemgruppen: weder umbenennbar noch in ihren Rechten
änderbar, auch nicht durch Admins über die UI — Mitgliedschaft bleibt frei
verwaltbar. Mit direktem POST verifiziert: Umbenennen/Löschen/Rechte-Reset
von "Benutzer" werden blockiert, Mitgliederverwaltung funktioniert weiter.
- groups.html zeigt den Baum jetzt als 3 Zeilen (Geräte/Logs/Einstellungen)
mit eingerückten Unterpunkten statt einer flachen Liste von Kategorien mit
wiederholtem Bereichsnamen im Label.
- Migration in _ensure_schema() (Altrechte übertragen, neue Bereichs-Rechte
für bestehende Gruppen nachtragen) läuft jetzt über einen Einmal-Guard in
der settings-Tabelle — lief anfangs bei jedem Neustart erneut und hat
damit den Kill-Switch-Mechanismus untergraben (ein deaktiviertes
Bereichs-Recht wäre bei jedem Neustart automatisch wieder gesetzt worden,
solange irgendein Kind-Recht noch aktiv war); im Test entdeckt und behoben.
- create_db.py synchronisiert (is_system-Spalte, neuer Rechtesatz für
Frischinstallationen).
- README: Rechtesystem-Abschnitt komplett neu beschrieben (Baum, Kill-Switch,
Systemgruppen, Eskalationsschutz).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
2.8 KiB
HTML
59 lines
2.8 KiB
HTML
{% extends "base.html" %}
|
|
{% set active_page = "settings_importexport" %}
|
|
{% block page_title %}Im-/Export{% endblock %}
|
|
{% block page_sub %}<div class="topbar-sub">Umzug auf eine neue Umgebung</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;">Export</h2>
|
|
<div class="hint">Geräte, Switche und Zugangsdaten als verschlüsseltes Bundle sichern — z.B. für einen Umzug auf eine neue Umgebung.</div>
|
|
</div>
|
|
</div>
|
|
<form method="post" action="{{ url_for('export_data') }}">
|
|
<div class="field">
|
|
<label for="export_passphrase">Passphrase</label>
|
|
<input type="password" name="export_passphrase" id="export_passphrase" required>
|
|
<div class="field-hint">Wird zum Verschlüsseln der Export-Datei benötigt — für den späteren Import dieselbe Passphrase erneut eingeben.</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-secondary btn-block">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
|
|
Export herunterladen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="card card-pad">
|
|
<div class="section-head" style="margin-bottom:16px;">
|
|
<div>
|
|
<h2 style="font-size:16px;">Import</h2>
|
|
<div class="hint">Ein zuvor exportiertes Bundle einlesen. Bestehende Einträge mit gleichem Namen/Hostname/MAC werden aktualisiert, neue werden angelegt.</div>
|
|
</div>
|
|
</div>
|
|
{% if current_user.has_permission('settings_importexport.edit') %}
|
|
<form method="post" action="{{ url_for('import_data') }}" enctype="multipart/form-data"
|
|
data-confirm="Import wirklich starten? Bestehende Einträge mit gleichem Namen/Hostname/MAC werden überschrieben.">
|
|
<div class="field">
|
|
<label for="import_file">Export-Datei</label>
|
|
<input type="file" name="import_file" id="import_file" accept=".json" required>
|
|
</div>
|
|
<div class="field">
|
|
<label for="import_passphrase">Passphrase</label>
|
|
<input type="password" name="import_passphrase" id="import_passphrase" required>
|
|
</div>
|
|
<button type="submit" class="btn btn-secondary btn-block">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 9V5a2 2 0 00-2-2H5a2 2 0 00-2 2v4"/><path d="M7 14l5-5 5 5"/><path d="M12 9v12"/></svg>
|
|
Import starten
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<p class="text-faint" style="font-size:12.5px;">Für den Import fehlt das Recht „Im-/Export ändern“.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
</div>
|
|
{% endblock %}
|