Rebrand to TESM: infra rename, LDAP/AD login, selective import/export, Papierkorb

- Vollständiges Rebranding "PoE Manager" -> TESM (TimEShepManager): neue
  Logos/Favicon (theme-aware), Sidebar/Login-Branding, Copyright-Zeile.
- Infrastruktur-Umbenennung: srv/poe_manager -> srv/tesm, alle POE_*-Env-Vars
  -> TESM_* (POE_SCRIPT bewusst unverändert, echtes PoE-Skript), systemd
  Units poe_web/rpi-check* -> tesm/tesm-check*, nginx-Site, netplan/logrotate
  Configs, Gitea-Repo alientim/Aruba-PoE-Modern -> alientim/tesm.
- LDAP/Active-Directory Same-Sign-On: Search+Bind, AD-Gruppen->App-Gruppen-
  Zuordnung (additiv), Konto-Sperren, AD-Vorab-Suche/-Anlage, eigene
  Einstellungsseite.
- Selektives Import/Export (8 Kategorien, Zwei-Schritt-Vorschau) mit eigenem
  R/E/X-Rechtemodell (X = Export, getrennt von E = Import).
- Papierkorb: Soft-Delete statt Hard-Delete für Geräte/Switche/Zugangsdaten/
  Benutzer/Gruppen (AD-Nutzer ausgenommen), eigene Seite unterhalb Wartung,
  konfigurierbare Aufbewahrungsfrist.
- rpi_ip -> ip Spalten-/Code-Umbenennung (Geräte sind längst nicht mehr auf
  Raspberry Pis beschränkt).
- README auf aktuellen Stand gebracht.
This commit is contained in:
2026-08-12 22:21:52 +02:00
parent 49634da0d9
commit d1b10bd970
50 changed files with 3641 additions and 728 deletions
+98
View File
@@ -0,0 +1,98 @@
<!-- Live-Update-fähiges Fragment: wird von index.html initial eingebunden UND
unverändert von /dashboard/tiles (AJAX) nachgeladen, damit das Dashboard
ohne vollen Seiten-Reload aktuell bleibt (siehe dashboard-tiles.js im
scripts-Block von index.html). -->
<div id="dashboard-tiles" data-last-run-ms="{{ last_run_epoch_ms or '' }}">
<div class="stat-row">
<div class="stat-card" data-status-filter="online">
<div class="stat-label">Online</div>
<div class="stat-value online">{{ stats.online }}</div>
</div>
<div class="stat-card" data-status-filter="offline">
<div class="stat-label">Offline</div>
<div class="stat-value offline">{{ stats.offline }}</div>
</div>
{% if current_user.is_authenticated %}
<div class="stat-card" data-status-filter="disabled">
<div class="stat-label">Deaktiviert</div>
<div class="stat-value disabled">{{ stats.disabled }}</div>
</div>
{% endif %}
<div class="stat-card" data-status-filter="">
<div class="stat-label">Gesamt</div>
<div class="stat-value">{{ stats.total }}</div>
</div>
</div>
{% macro device_tile(d) %}
{% set st = status.get(d['mac'], 'unbekannt') %}
<div class="device-card {% if d['is_active'] == 0 %}is-disabled{% endif %} {% if not current_user.is_authenticated %}is-readonly{% endif %}"
data-status="{{ 'disabled' if d['is_active'] == 0 else st }}"
data-mac="{{ d['mac'] }}"
data-name="{{ d['name'] }}"
{% if current_user.is_authenticated %}data-ip="{{ d['ip'] }}"{% endif %}
data-switch="{{ d['switch_hostname'] or '-' }}"
data-port="{{ d['port'] or '-' }}"
data-active="{{ d['is_active'] }}"
data-checked="{{ last_checked.get(d['mac']) or '-' }}"
{% if last_seen.get(d['mac']) %}title="{{ last_seen[d['mac']] }}"{% endif %}>
<div class="dc-top">
<div class="dc-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2.5"/><path d="M8 2v3M16 2v3M8 19v3M16 19v3M2 8h3M2 16h3M19 8h3M19 16h3"/></svg>
</div>
{% if d['is_active'] == 0 %}
<span class="pill disabled">Deaktiviert</span>
{% else %}
<span class="pill {{ st }}">{{ {'online':'Online','offline':'Offline','unbekannt':'Unbekannt'}[st] }}</span>
{% endif %}
</div>
<div class="dc-name">{{ d['name'] }}</div>
{% if current_user.is_authenticated %}<div class="dc-ip">{{ d['ip'] }}</div>{% endif %}
</div>
{% endmacro %}
{% if device_count %}
{% macro section_chevron() %}<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>{% endmacro %}
{% if offline_devices %}
<div class="dash-section" data-status="offline">
<div class="dash-section-title">{{ section_chevron() }}Offline <span class="pill offline">{{ offline_devices|length }}</span></div>
<div class="device-grid">
{% for d in offline_devices %}{{ device_tile(d) }}{% endfor %}
</div>
</div>
{% endif %}
{% if online_devices %}
<div class="dash-section" data-status="online">
<div class="dash-section-title">{{ section_chevron() }}Online <span class="pill online">{{ online_devices|length }}</span></div>
<div class="device-grid">
{% for d in online_devices %}{{ device_tile(d) }}{% endfor %}
</div>
</div>
{% endif %}
{% if disabled_devices %}
<div class="dash-section" data-status="disabled">
<div class="dash-section-title">{{ section_chevron() }}Deaktiviert <span class="pill disabled">{{ disabled_devices|length }}</span></div>
<div class="device-grid">
{% for d in disabled_devices %}{{ device_tile(d) }}{% endfor %}
</div>
</div>
{% endif %}
<p id="noSearchResults" class="text-faint hidden" style="text-align:center; padding:30px 0;">Kein Gerät gefunden.</p>
{% else %}
<div class="card card-pad" style="text-align:center; color:var(--text-faint);">
{% if current_user.is_authenticated %}
Noch keine Geräte angelegt. Füge welche unter <a href="{{ url_for('devices') }}" style="color:var(--accent-strong); font-weight:600;">Devices</a> hinzu.
{% else %}
Aktuell keine Geräte verfügbar.
{% endif %}
</div>
{% endif %}
</div>