Baut auf licensing.py (Phase 0) auf und schaltet TESM erstmals auf ein lizenziertes Modulmodell um. Standard-Funktionen (Dashboard, Clients, Switche, Zugangsdaten, Benutzer, Gruppen, Auditlog, Live-Log, Verlauf, Import) bleiben immer frei nutzbar. - license_active()/module_licensed()-Gate-Funktionen + In-Memory- Lizenzcache (_load_license, _write_license_file, _delete_license_file), Jinja-Globals fuer Templates. - Vollstaendige Module (DHCP inkl. Kea-Log, Fileshare, Wartung) werden ohne passendes Modul in der Lizenz komplett aus der Navigation ausgeblendet (_nav_key_visible) UND serverseitig auf Routenebene abgewiesen (_require_module) -- ein direkter URL-Aufruf hilft nicht. - 'Sichtbar, aber ohne Lizenz inaktiv': LDAP- und NGINX-Einstellungen, Papierkorb-Bearbeiten, Export sowie das Pruefintervall-Feld (aus dem gemeinsamen Systemeinstellungen-Gate herausgeloest, Hostname/Zeitzone/ Netzwerk/Logs bleiben frei) zeigen ohne Lizenz einen Hinweis statt der Aktion. - Export bleibt als bewusste Ausnahme auch im lizenzlosen Zustand nutzbar, wenn dieser durch eine selbst ausgeloeste Deaktivierung (Systemwechsel) entstanden ist (license_export_grace). - Automatischer PoE-Neustart bei Ausfall wird in poe.sh (nicht in Python) gegatet -- einmal pro Schleifendurchlauf gelesen, an check_device() durchgereicht; der manuelle Neustart-Button (devices.restart) bleibt komplett unangetastet. - Topbar-Lizenzanzeige (Prioritaet: fehlt > abgelaufen > laeuft in <=30 Tagen ab > Heartbeat seit >=14 Tagen ausgeblieben (rein informativ) > alles ok), eigene CSS-Blink-Varianten fuer Rot/Orange (die vorhandene pulse-dot-Animation war fest auf Gruen verdrahtet). - Neue Settings-Unterseite 'Lizenz': Status, Lizenzdatei-Upload, Online- Aktivierung/-Deaktivierung mit automatischem Fallback auf einen manuell auszutauschenden Code (ein Protokoll, zwei Transportwege, siehe licensing.py), Anbieter-Kontaktkarte. - 6-stuendiger Heartbeat-Hintergrundthread (_license_heartbeat_loop, exakt nach dem Vorbild von _audit_archive_loop). Isoliert auf dem Testsystem (192.168.82.51) verifiziert: alle fuenf Topbar-/Sichtbarkeitszustaende (keine/gueltige/bald ablaufende/abgelaufene/ heartbeat-stale Lizenz) per Playwright durchgespielt, kompletter Online- Fallback-auf-Offline-Code-Austausch fuer Aktivierung UND Deaktivierung gegen einen simulierten Master erfolgreich durchlaufen (inkl. Export- Grace-Ausnahme nach Deaktivierung). Bewusst NICHT nach main gemergt/getaggt/ausgerollt: ohne den in Phase 2 noch zu bauenden Master-Lizenzserver kann POETEST noch keine echte Lizenz beziehen -- ein Rollout jetzt wuerde dort DHCP/Fileshare/Wartung/LDAP/ NGINX/Papierkorb/Export/automatischen Neustart sofort abschalten. Bleibt auf dev, bis Phase 2 (Master) + Phase 3 (POETEST-Enterprise-Lizenz) so weit sind, dass beides zusammen ausgerollt werden kann. Teil des Lizenz-/Modulsystems, siehe Plan toasty-twirling-hickey.md (Phase 1 von 3). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
455 lines
25 KiB
HTML
455 lines
25 KiB
HTML
{% extends "base.html" %}
|
|
{% set active_page = "settings_ldap" %}
|
|
{% set can_edit = current_user.has_permission('settings_ldap.edit') and license_active() %}
|
|
{% block page_title %}LDAP / Active Directory{% endblock %}
|
|
{% block page_sub %}<div class="topbar-sub">Anmeldung mit dem Domänen-Passwort, zusätzlich zu lokalen Konten</div>{% endblock %}
|
|
|
|
{% block content %}
|
|
{% import "_hint_icon.html" as hi %}
|
|
<div class="settings-grid settings-grid--wide">
|
|
|
|
<div class="card card-pad">
|
|
<div class="section-head" style="margin-bottom:16px;">
|
|
<div>
|
|
<h2 style="font-size:16px;">Verbindung</h2>
|
|
<div class="hint">Server, Bind-Konto und Suchparameter für die Anbindung an AD/LDAP.</div>
|
|
</div>
|
|
</div>
|
|
{% if can_edit %}
|
|
<form method="post">
|
|
<div class="field">
|
|
<label class="switch-check" style="display:inline-flex; align-items:center; gap:8px;">
|
|
<input type="checkbox" name="ldap_enabled" {% if ldap.enabled %}checked{% endif %}>
|
|
<span class="track"></span>
|
|
<span>LDAP-Anmeldung aktivieren</span>
|
|
</label>
|
|
</div>
|
|
<div class="field"><label>Server {{ hi.hint_icon("DNS-Name oder IP-Adresse — beides wird genau so gespeichert und beim Verbinden verwendet.", "Server") }}</label>
|
|
<input type="text" name="ldap_server" value="{{ ldap.server }}" placeholder="z.B. 192.168.1.1 oder dc01.firma.local">
|
|
</div>
|
|
<div class="field">
|
|
<label class="switch-check" style="display:inline-flex; align-items:center; gap:8px;">
|
|
<input type="checkbox" name="ldap_use_ssl" id="ldap_use_ssl" {% if ldap.use_ssl %}checked{% endif %}
|
|
onchange="document.getElementById('ldap_port').value = this.checked ? 636 : 389;">
|
|
<span class="track"></span>
|
|
<span>LDAPS/TLS verwenden {{ hi.hint_icon("Ohne LDAPS wird das Passwort unverschlüsselt übertragen — nur für interne Tests geeignet, vor Produktivbetrieb LDAPS auf dem Domain Controller einrichten. Stellt beim Umschalten den Port automatisch auf 636/389 — unten weiterhin manuell änderbar.", "LDAPS/TLS verwenden") }}</span>
|
|
</label>
|
|
</div>
|
|
<div class="field"><label>Port</label>
|
|
<input type="number" name="ldap_port" id="ldap_port" min="1" max="65535" value="{{ ldap.port }}">
|
|
</div>
|
|
<div class="field">
|
|
<label class="switch-check" style="display:inline-flex; align-items:center; gap:8px;">
|
|
<input type="checkbox" name="ldap_tls_skip_verify" {% if ldap.tls_skip_verify %}checked{% endif %}>
|
|
<span class="track"></span>
|
|
<span>Zertifikatsprüfung überspringen (nur LDAPS) {{ hi.hint_icon("Akzeptiert jedes Server-Zertifikat, auch selbstsignierte/nicht vertrauenswürdige — praktisch für interne Tests, schützt dann aber nicht mehr vor einem gefälschten Server. Vor Produktivbetrieb ein echtes, vertrauenswürdiges Zertifikat einrichten und diese Option deaktivieren.", "Zertifikatsprüfung überspringen") }}</span>
|
|
</label>
|
|
</div>
|
|
<div class="field"><label>Bind-Konto (Service-Account) {{ hi.hint_icon("Ein normales, unprivilegiertes Domänenkonto reicht — es wird nur zum Suchen von Benutzern verwendet, keine Admin-Rechte nötig. Ein neu gespeichertes Konto ersetzt das bisherige vollständig.", "Bind-Konto (Service-Account)") }}</label>
|
|
<input type="text" name="ldap_bind_dn" value="{{ ldap.bind_dn }}" placeholder="z.B. ldap@ad.firma.local">
|
|
</div>
|
|
<div class="field"><label>Bind-Passwort</label>
|
|
<input type="password" name="ldap_bind_password" placeholder="{{ '(unverändert lassen)' if ldap.bind_password_enc else '' }}">
|
|
</div>
|
|
<div class="field"><label>Base-DN</label>
|
|
<input type="text" name="ldap_base_dn" value="{{ ldap.base_dn }}" placeholder="Leer = automatisch ermitteln">
|
|
</div>
|
|
<div class="field"><label>Attribut für Benutzername {{ hi.hint_icon("Für Active Directory: sAMAccountName. Für generisches LDAP (z.B. OpenLDAP): meist uid. Anmeldung per userPrincipalName (E-Mail/UPN) funktioniert unabhängig davon immer zusätzlich.", "Attribut für Benutzername") }}</label>
|
|
<input type="text" name="ldap_user_filter_attr" value="{{ ldap.filter_attr }}" placeholder="sAMAccountName">
|
|
</div>
|
|
<div class="field"><label>Standardgruppe für neue AD-Benutzer {{ hi.hint_icon("Wird nur zugewiesen, wenn unten keine AD-Gruppenzuordnung greift — siehe Karte „AD-Gruppenzuordnungen“.", "Standardgruppe für neue AD-Benutzer") }}</label>
|
|
<select name="ldap_default_group">
|
|
<option value="">Systemstandard ({{ ldap_groups|selectattr('is_default')|map(attribute='name')|first or 'Benutzer' }})</option>
|
|
{% for g in ldap_groups %}
|
|
<option value="{{ g['id'] }}" {% if ldap.default_group == g['id']|string %}selected{% endif %}>{{ g['name'] }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="field"><label>Erforderliche AD-Gruppe für Login (optional)</label>
|
|
<div class="flex gap-2">
|
|
<select name="ldap_required_login_group" id="requiredGroupSelect" style="flex:1;">
|
|
<option value="">Keine (jeder gültige AD-Benutzer darf sich anmelden)</option>
|
|
{% if ldap.required_login_group %}
|
|
<option value="{{ ldap.required_login_group }}" selected>{{ ldap.required_login_group }}</option>
|
|
{% endif %}
|
|
</select>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="requiredGroupLoadBtn">Gruppen laden</button>
|
|
</div>
|
|
<div class="field-hint" id="requiredGroupLoadStatus">Ist hier eine Gruppe ausgewählt, scheitert die Anmeldung für alle Benutzer, die ihr NICHT angehören (rekursiv, auch über verschachtelte Gruppen) — wie bei falschen Zugangsdaten, ohne Hinweis auf den eigentlichen Grund.</div>
|
|
</div>
|
|
<div class="flex gap-2" style="flex-wrap:wrap;">
|
|
<button type="submit" name="save_ldap" value="1" 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>
|
|
Speichern
|
|
</button>
|
|
<button type="submit" name="test_ldap" value="1" class="btn btn-secondary" formnovalidate>
|
|
Verbindung testen
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% if ldap.bind_dn %}
|
|
<form method="post" data-confirm="LDAP-Bind-Konto wirklich löschen? Die LDAP-Anmeldung wird dabei deaktiviert." style="margin-top:10px;">
|
|
<input type="hidden" name="clear_ldap_bind" value="1">
|
|
<button type="submit" class="btn btn-sm" style="color:var(--danger); background:transparent; border-color:var(--danger-dim);">
|
|
Bind-Konto löschen
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
{% else %}
|
|
<p class="text-faint" style="font-size:12.5px;">
|
|
{% if current_user.has_permission('settings_ldap.edit') %}
|
|
Nur mit Lizenz verfügbar — siehe <a href="{{ url_for('settings_license') }}">Lizenz</a>.
|
|
{% else %}
|
|
Für Änderungen fehlt das Recht „LDAP/AD-Konfiguration speichern“.
|
|
{% endif %}
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="card card-pad">
|
|
<div class="section-head" style="margin-bottom:16px;">
|
|
<div>
|
|
<h2 style="font-size:16px;">AD-Gruppenzuordnungen {{ hi.hint_icon("Ist ein AD-Benutzer (rekursiv, auch über verschachtelte Gruppen) Mitglied einer hier zugeordneten AD-Gruppe, erhält er beim Login zusätzlich die zugeordnete App-Rechtegruppe — additiv, mehrere Zuordnungen können gleichzeitig greifen. Wird keine Zuordnung getroffen, gilt die Standardgruppe oben.", "AD-Gruppenzuordnungen") }}</h2>
|
|
</div>
|
|
{% if can_edit %}
|
|
<button type="button" class="btn btn-primary" data-open-modal="addLdapMappingModal">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
Zuordnung hinzufügen
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if mappings %}
|
|
<div class="table-wrap">
|
|
<div style="overflow-x:auto;">
|
|
<table class="data-table">
|
|
<thead><tr><th>AD-Gruppe</th><th>App-Rechtegruppe</th><th style="width:1%;">Aktionen</th></tr></thead>
|
|
<tbody>
|
|
{% for m in mappings %}
|
|
<tr>
|
|
<td>{{ m.ad_group_name }}<div class="text-faint mono" style="font-size:11px;">{{ m.ad_group_dn }}</div></td>
|
|
<td>{{ m.app_group_name or '—' }}</td>
|
|
<td>
|
|
{% if can_edit %}
|
|
<div class="row-actions">
|
|
<button type="button" class="icon-btn" title="Bearbeiten"
|
|
onclick="openEditLdapMappingModal('{{ m.id }}','{{ m.ad_group_dn|e }}','{{ m.ad_group_name|e }}','{{ m.app_group_id }}')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 013 3L7 19l-4 1 1-4z"/></svg>
|
|
</button>
|
|
<form method="post" data-confirm="Zuordnung „{{ m.ad_group_name }} → {{ m.app_group_name }}“ löschen?">
|
|
<input type="hidden" name="delete_ldap_group_mapping" value="{{ m.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 keine Zuordnung angelegt — alle neuen AD-Benutzer erhalten nur die Standardgruppe.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="card card-pad">
|
|
<div class="section-head" style="margin-bottom:16px;">
|
|
<div>
|
|
<h2 style="font-size:16px;">Fileshare-Gruppenzuordnung {{ hi.hint_icon("Ist ein AD-Benutzer (rekursiv) Mitglied einer hier zugeordneten AD-Gruppe, wird die zugehörige Freigabe beim Login für ihn gemountet — sofern er zusätzlich das TESM-Recht „Dateifreigaben lesen“ hat (siehe Gruppen → Rechte, Bereich „Dateifreigaben“). Fehlt das Recht, wird nicht gemountet und der Menüpunkt „Dateifreigaben“ erscheint nicht, unabhängig von der AD-Gruppenmitgliedschaft.", "Fileshare-Gruppenzuordnung") }}</h2>
|
|
</div>
|
|
{% if can_edit %}
|
|
<button type="button" class="btn btn-primary" data-open-modal="addFileshareMappingModal">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
Zuordnung hinzufügen
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if fileshare_mappings %}
|
|
<div class="table-wrap">
|
|
<div style="overflow-x:auto;">
|
|
<table class="data-table">
|
|
<thead><tr><th>AD-Gruppe</th><th>Freigabe</th><th>Pfad</th><th style="width:1%;">Aktionen</th></tr></thead>
|
|
<tbody>
|
|
{% for m in fileshare_mappings %}
|
|
<tr>
|
|
<td>{{ m.ad_group_name }}<div class="text-faint mono" style="font-size:11px;">{{ m.ad_group_dn }}</div></td>
|
|
<td>{{ m.share_label }}</td>
|
|
<td class="mono" style="font-size:12px;">{{ m.share_unc }}</td>
|
|
<td>
|
|
{% if can_edit %}
|
|
<div class="row-actions">
|
|
<button type="button" class="icon-btn" title="Bearbeiten"
|
|
onclick="openEditFileshareMappingModal('{{ m.id }}','{{ m.ad_group_dn|e }}','{{ m.ad_group_name|e }}','{{ m.share_label|e }}','{{ m.share_unc|e }}')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 013 3L7 19l-4 1 1-4z"/></svg>
|
|
</button>
|
|
<form method="post" data-confirm="Fileshare-Zuordnung „{{ m.ad_group_name }} → {{ m.share_label }}“ löschen?">
|
|
<input type="hidden" name="delete_fileshare_mapping" value="{{ m.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 keine Fileshare-Zuordnung angelegt — für niemanden wird eine Freigabe gemountet.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{% if can_edit %}
|
|
<div class="modal-overlay" id="addLdapMappingModal">
|
|
<div class="modal">
|
|
<form method="post">
|
|
<input type="hidden" name="add_ldap_group_mapping" value="1">
|
|
<div class="modal-header">
|
|
<h3>AD-Gruppenzuordnung hinzufügen</h3>
|
|
<button type="button" class="modal-close" data-close-modal>×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="field">
|
|
<label>AD-Gruppe</label>
|
|
<div class="flex gap-2">
|
|
<select name="ad_group_dn" id="ldapMappingAdGroup" required style="flex:1;">
|
|
<option value="">— zuerst laden —</option>
|
|
</select>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="ldapMappingLoadGroupsBtn">Gruppen laden</button>
|
|
</div>
|
|
<input type="hidden" name="ad_group_name" id="ldapMappingAdGroupName">
|
|
<div class="field-hint" id="ldapMappingLoadStatus">Fragt live per Bind-Konto alle Gruppen aus dem Verzeichnis ab.</div>
|
|
</div>
|
|
<div class="field"><label>App-Rechtegruppe</label>
|
|
<select name="app_group_id" required>
|
|
<option value="">— auswählen —</option>
|
|
<option value="admin">Admin (alle Rechte)</option>
|
|
{% for g in ldap_groups %}
|
|
<option value="{{ g['id'] }}">{{ g['name'] }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</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>
|
|
|
|
<div class="modal-overlay" id="addFileshareMappingModal">
|
|
<div class="modal">
|
|
<form method="post">
|
|
<input type="hidden" name="add_fileshare_mapping" value="1">
|
|
<div class="modal-header">
|
|
<h3>Fileshare-Zuordnung hinzufügen</h3>
|
|
<button type="button" class="modal-close" data-close-modal>×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="field">
|
|
<label>AD-Gruppe</label>
|
|
<div class="flex gap-2">
|
|
<select name="fs_ad_group_dn" id="fsMappingAdGroup" required style="flex:1;">
|
|
<option value="">— zuerst laden —</option>
|
|
</select>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="fsMappingLoadGroupsBtn">Gruppen laden</button>
|
|
</div>
|
|
<input type="hidden" name="fs_ad_group_name" id="fsMappingAdGroupName">
|
|
<div class="field-hint" id="fsMappingLoadStatus">Fragt live per Bind-Konto alle Gruppen aus dem Verzeichnis ab.</div>
|
|
</div>
|
|
<div class="field"><label>Bezeichnung {{ hi.hint_icon("Anzeigename in der Freigaben-Auswahl — auch Ordnername unter dem Mount-Punkt.", "Bezeichnung") }}</label>
|
|
<input type="text" name="fs_share_label" placeholder="z.B. Vertrieb" required>
|
|
</div>
|
|
<div class="field"><label>Freigabe-Pfad (UNC) {{ hi.hint_icon("Beide Schreibweisen funktionieren — \\\\server\\freigabe wird automatisch in das von Linux benötigte //server/freigabe umgewandelt.", "Freigabe-Pfad (UNC)") }}</label>
|
|
<input type="text" name="fs_share_unc" placeholder="//fileserver/freigabe" required>
|
|
</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>
|
|
|
|
<div class="modal-overlay" id="editLdapMappingModal">
|
|
<div class="modal">
|
|
<form method="post">
|
|
<input type="hidden" name="edit_ldap_group_mapping" id="editLdapMappingId" value="">
|
|
<div class="modal-header">
|
|
<h3>AD-Gruppenzuordnung bearbeiten</h3>
|
|
<button type="button" class="modal-close" data-close-modal>×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="field">
|
|
<label>AD-Gruppe</label>
|
|
<div class="flex gap-2">
|
|
<select name="ad_group_dn" id="editLdapMappingAdGroup" required style="flex:1;"></select>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="editLdapMappingLoadGroupsBtn">Gruppen laden</button>
|
|
</div>
|
|
<input type="hidden" name="ad_group_name" id="editLdapMappingAdGroupName">
|
|
<div class="field-hint" id="editLdapMappingLoadStatus">Fragt live per Bind-Konto alle Gruppen aus dem Verzeichnis ab.</div>
|
|
</div>
|
|
<div class="field"><label>App-Rechtegruppe</label>
|
|
<select name="app_group_id" id="editLdapMappingAppGroup" required>
|
|
<option value="">— auswählen —</option>
|
|
<option value="admin">Admin (alle Rechte)</option>
|
|
{% for g in ldap_groups %}
|
|
<option value="{{ g['id'] }}">{{ g['name'] }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</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>
|
|
|
|
<div class="modal-overlay" id="editFileshareMappingModal">
|
|
<div class="modal">
|
|
<form method="post">
|
|
<input type="hidden" name="edit_fileshare_mapping" id="editFsMappingId" value="">
|
|
<div class="modal-header">
|
|
<h3>Fileshare-Zuordnung bearbeiten</h3>
|
|
<button type="button" class="modal-close" data-close-modal>×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="field">
|
|
<label>AD-Gruppe</label>
|
|
<div class="flex gap-2">
|
|
<select name="fs_ad_group_dn" id="editFsMappingAdGroup" required style="flex:1;"></select>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="editFsMappingLoadGroupsBtn">Gruppen laden</button>
|
|
</div>
|
|
<input type="hidden" name="fs_ad_group_name" id="editFsMappingAdGroupName">
|
|
<div class="field-hint" id="editFsMappingLoadStatus">Fragt live per Bind-Konto alle Gruppen aus dem Verzeichnis ab.</div>
|
|
</div>
|
|
<div class="field"><label>Bezeichnung {{ hi.hint_icon("Anzeigename in der Freigaben-Auswahl — auch Ordnername unter dem Mount-Punkt.", "Bezeichnung") }}</label>
|
|
<input type="text" name="fs_share_label" id="editFsMappingLabel" placeholder="z.B. Vertrieb" required>
|
|
</div>
|
|
<div class="field"><label>Freigabe-Pfad (UNC) {{ hi.hint_icon("Beide Schreibweisen funktionieren — \\\\server\\freigabe wird automatisch in das von Linux benötigte //server/freigabe umgewandelt.", "Freigabe-Pfad (UNC)") }}</label>
|
|
<input type="text" name="fs_share_unc" id="editFsMappingUnc" placeholder="//fileserver/freigabe" required>
|
|
</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>
|
|
|
|
<script>
|
|
(function () {
|
|
function wireAdGroupLoader(btnId, selectId, nameFieldId, statusId, includeEmptyOption) {
|
|
var btn = document.getElementById(btnId);
|
|
var select = document.getElementById(selectId);
|
|
var nameField = nameFieldId ? document.getElementById(nameFieldId) : null;
|
|
var status = document.getElementById(statusId);
|
|
if (!btn) return;
|
|
|
|
btn.addEventListener('click', function () {
|
|
// Aktuellen Wert (z.B. die vorher gespeicherte, noch nicht per
|
|
// Klartext-Namen aufgeloeste Gruppe) merken -- bleibt nach dem
|
|
// Neuaufbau der Optionsliste ausgewaehlt, falls sie unter den
|
|
// geladenen Gruppen auftaucht.
|
|
var currentValue = select.value;
|
|
status.textContent = 'Lade Gruppen …';
|
|
fetch("{{ url_for('settings_ldap_ad_groups') }}")
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (groups) {
|
|
if (!Array.isArray(groups)) {
|
|
status.textContent = groups.error || 'Fehler beim Laden.';
|
|
return;
|
|
}
|
|
select.innerHTML = '';
|
|
if (includeEmptyOption) {
|
|
var emptyOpt = document.createElement('option');
|
|
emptyOpt.value = '';
|
|
emptyOpt.textContent = 'Keine (jeder gültige AD-Benutzer darf sich anmelden)';
|
|
select.appendChild(emptyOpt);
|
|
}
|
|
if (!groups.length) {
|
|
if (!includeEmptyOption) select.innerHTML = '<option value="">Keine Gruppen gefunden</option>';
|
|
status.textContent = 'Keine Gruppen gefunden — Verbindung/Bind-Konto prüfen.';
|
|
return;
|
|
}
|
|
groups.forEach(function (g) {
|
|
var opt = document.createElement('option');
|
|
opt.value = g.dn;
|
|
opt.textContent = g.name;
|
|
opt.dataset.name = g.name;
|
|
if (g.dn === currentValue) opt.selected = true;
|
|
select.appendChild(opt);
|
|
});
|
|
if (nameField) nameField.value = (select.options[select.selectedIndex] && select.options[select.selectedIndex].dataset.name) || '';
|
|
status.textContent = groups.length + ' Gruppe(n) geladen.';
|
|
})
|
|
.catch(function () { status.textContent = 'Fehler beim Laden — Verbindung/Bind-Konto prüfen.'; });
|
|
});
|
|
|
|
select.addEventListener('change', function () {
|
|
if (!nameField) return;
|
|
var opt = select.options[select.selectedIndex];
|
|
nameField.value = (opt && opt.dataset.name) || '';
|
|
});
|
|
}
|
|
|
|
wireAdGroupLoader('ldapMappingLoadGroupsBtn', 'ldapMappingAdGroup', 'ldapMappingAdGroupName', 'ldapMappingLoadStatus', false);
|
|
wireAdGroupLoader('fsMappingLoadGroupsBtn', 'fsMappingAdGroup', 'fsMappingAdGroupName', 'fsMappingLoadStatus', false);
|
|
wireAdGroupLoader('requiredGroupLoadBtn', 'requiredGroupSelect', null, 'requiredGroupLoadStatus', true);
|
|
wireAdGroupLoader('editLdapMappingLoadGroupsBtn', 'editLdapMappingAdGroup', 'editLdapMappingAdGroupName', 'editLdapMappingLoadStatus', false);
|
|
wireAdGroupLoader('editFsMappingLoadGroupsBtn', 'editFsMappingAdGroup', 'editFsMappingAdGroupName', 'editFsMappingLoadStatus', false);
|
|
})();
|
|
|
|
// Bearbeiten-Modals vorbefuellen -- die AD-Gruppe steht dabei zunaechst nur
|
|
// als DN+Name aus der Datenbank zur Verfuegung (ohne erneute LDAP-Abfrage);
|
|
// "Gruppen laden" ersetzt die Auswahlliste bei Bedarf durch die vollstaendige,
|
|
// aktuelle AD-Gruppenliste und behaelt den bisherigen Wert dabei bei (siehe
|
|
// wireAdGroupLoader oben).
|
|
function seedMappingSelect(selectId, dn, name) {
|
|
var select = document.getElementById(selectId);
|
|
select.innerHTML = '';
|
|
var opt = document.createElement('option');
|
|
opt.value = dn;
|
|
opt.textContent = name || dn;
|
|
opt.dataset.name = name || dn;
|
|
opt.selected = true;
|
|
select.appendChild(opt);
|
|
}
|
|
|
|
function openEditLdapMappingModal(id, dn, name, appGroupId) {
|
|
document.getElementById('editLdapMappingId').value = id;
|
|
seedMappingSelect('editLdapMappingAdGroup', dn, name);
|
|
document.getElementById('editLdapMappingAdGroupName').value = name;
|
|
document.getElementById('editLdapMappingAppGroup').value = appGroupId;
|
|
document.getElementById('editLdapMappingLoadStatus').textContent = 'Fragt live per Bind-Konto alle Gruppen aus dem Verzeichnis ab.';
|
|
PoeUI.openModal('editLdapMappingModal');
|
|
}
|
|
|
|
function openEditFileshareMappingModal(id, dn, name, label, unc) {
|
|
document.getElementById('editFsMappingId').value = id;
|
|
seedMappingSelect('editFsMappingAdGroup', dn, name);
|
|
document.getElementById('editFsMappingAdGroupName').value = name;
|
|
document.getElementById('editFsMappingLabel').value = label;
|
|
document.getElementById('editFsMappingUnc').value = unc;
|
|
document.getElementById('editFsMappingLoadStatus').textContent = 'Fragt live per Bind-Konto alle Gruppen aus dem Verzeichnis ab.';
|
|
PoeUI.openModal('editFileshareMappingModal');
|
|
}
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|