Geräte: SSH-Zugangsdaten mit Kategorien, PoE-Neustart nur wenn möglich

- credentials: neue Spalte 'category' (Switch/Linux-Client/
  Windows-Client/Router), Grundlage für die geplante SSH-basierte
  Wartungsfunktion (Bulk-Updates); Löschschutz prüft jetzt zusätzlich
  devices.credential_id, nicht mehr nur switches.credential_id
- devices: optionale SSH-Zugangsdaten + SSH-Port je Gerät, inkl.
  "Verbindung testen"-Terminal wie bei Switches
- Dashboard: PoE-Neustart-Button wird für Geräte ohne zugewiesenen
  Switch/Port komplett ausgeblendet (nicht nur ausgegraut), da für
  diese kein PoE-Reset über poe.sh möglich ist; Button bleibt
  ausschließlich für den PoE-Neustart über den Switch zuständig
- Schließen-Button im Geräte-Modal immer rechts, unabhängig davon
  welche Aktions-Buttons gerade sichtbar sind
- "Port" im Geräte-Formular zu "Switchport" umbenannt und von
  "SSH-Port" klar abgegrenzt (unterschiedliche Konzepte: physischer
  Switch-Port fürs PoE vs. TCP-Port für SSH)
- .btn:disabled greift jetzt echtes Grau statt nur reduzierter
  Deckkraft der jeweiligen Button-Farbe

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 10:34:30 +02:00
co-authored by Claude Sonnet 5
parent d3cf78592f
commit 63ca7782ba
5 changed files with 426 additions and 43 deletions
+31 -5
View File
@@ -3,6 +3,7 @@
{% set can_create = current_user.has_permission('credentials.create') %}
{% set can_edit = current_user.has_permission('credentials.edit') %}
{% set can_delete = current_user.has_permission('credentials.edit') %}
{% set category_labels = dict(categories) %}
{% block page_title %}Zugangsdaten{% endblock %}
{% block page_sub %}<div class="topbar-sub">{{ credentials|length }} Zugangsdaten</div>{% endblock %}
@@ -27,20 +28,29 @@
<thead><tr>
<th data-sort-key="name">Name</th>
<th data-sort-key="username">Username</th>
<th data-sort-key="category">Kategorie</th>
<th data-sort-key="usage">Verwendet von</th>
<th style="width:1%;">Aktionen</th>
</tr></thead>
<tbody>
{% for c in credentials %}
<tr data-sort-name="{{ c['name']|lower }}" data-sort-username="{{ c['username']|lower }}" data-sort-usage="{{ c['switch_count'] }}">
{% set usage_total = c['switch_count'] + c['device_count'] %}
<tr data-sort-name="{{ c['name']|lower }}" data-sort-username="{{ c['username']|lower }}" data-sort-category="{{ c['category'] }}" data-sort-usage="{{ usage_total }}">
<td class="cell-name">{{ c['name'] }}</td>
<td class="mono">{{ c['username'] }}</td>
<td class="text-dim">{{ c['switch_count'] }} Switch{{ 'e' if c['switch_count'] != 1 else '' }}</td>
<td>{{ category_labels.get(c['category'], c['category']) }}</td>
<td class="text-dim">
{% if usage_total == 0 %}—{% else %}
{% if c['switch_count'] %}{{ c['switch_count'] }} Switch{{ 'e' if c['switch_count'] != 1 else '' }}{% endif %}
{% if c['switch_count'] and c['device_count'] %}, {% endif %}
{% if c['device_count'] %}{{ c['device_count'] }} Gerät{{ 'e' if c['device_count'] != 1 else '' }}{% endif %}
{% endif %}
</td>
<td>
<div class="row-actions">
{% if can_edit %}
<button class="icon-btn" title="Bearbeiten"
onclick="openEditCredentialModal({{ c['id'] }}, '{{ c['name'] }}', '{{ c['username'] }}')">
onclick="openEditCredentialModal({{ c['id'] }}, '{{ c['name'] }}', '{{ c['username'] }}', '{{ c['category'] }}')">
<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>
{% endif %}
@@ -56,7 +66,7 @@
</td>
</tr>
{% else %}
<tr class="empty-row"><td colspan="4">Noch keine Zugangsdaten vorhanden.</td></tr>
<tr class="empty-row"><td colspan="5">Noch keine Zugangsdaten vorhanden.</td></tr>
{% endfor %}
</tbody>
</table>
@@ -80,6 +90,14 @@
<div class="field"><label>Username</label>
<input type="text" name="username" required placeholder="z.B. admin">
</div>
<div class="field"><label>Kategorie</label>
<select name="category">
{% for key, label in categories %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
<div class="hint">Bestimmt u.a., ob diese Zugangsdaten unter „Wartung“ für Bulk-Updates nutzbar sind.</div>
</div>
<div class="field"><label>Passwort</label>
<input type="password" id="password_add" name="password" required>
</div>
@@ -115,6 +133,13 @@
<div class="field"><label>Username</label>
<input type="text" name="username" id="edit_cred_username" required>
</div>
<div class="field"><label>Kategorie</label>
<select name="category" id="edit_cred_category">
{% for key, label in categories %}
<option value="{{ key }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="field"><label>Neues Passwort</label>
<input type="password" id="password_edit" name="password" placeholder="Nur bei Änderung ausfüllen">
</div>
@@ -136,10 +161,11 @@
{% block scripts %}
<script>
function openEditCredentialModal(id, name, username) {
function openEditCredentialModal(id, name, username, category) {
document.getElementById("edit_cred_id").value = id;
document.getElementById("edit_cred_name").value = name;
document.getElementById("edit_cred_username").value = username;
document.getElementById("edit_cred_category").value = category;
document.getElementById("password_edit").value = "";
document.getElementById("password_confirm_edit").value = "";
PoeUI.openModal("editCredentialModal");