Navbar-Reihenfolge: Unterpunkte je Gruppe editierbar, Karte unter Profil/Passwort verschoben

- Neuer Settings-Schluessel nav_child_order (JSON dict group_key ->
  geordnete Liste der Kind-Keys), analog zu nav_order fuer die Top-Level-
  Reihenfolge. _ordered_nav_items() liefert die komplette, ungefilterte
  Navbar-Struktur inkl. angewandter Kind-Reihenfolge; inject_nav() filtert
  das anschliessend weiterhin nach Berechtigung wie bisher.
- save_nav_order() verarbeitet zusaetzlich ein Feld
  nav_child_order_<group_key> pro Gruppe mit Kindern.
- account.html: Navbar-Reihenfolge-Karte jetzt UNTER Profil/Passwort
  aendern (vorher darueber), zeigt die komplette Navbar inkl.
  eingerueckter, separat sortierbarer Unterpunkte pro Gruppe
  (verschachtelte <ul>, moveNavItem() bewegt automatisch nur innerhalb
  der eigenen Gruppe dank DOM-Nesting).
- Live per Playwright verifiziert: Zugangsdaten innerhalb Geraete nach
  oben verschoben, gespeichert, Sidebar zeigt danach tatsaechlich die
  neue Reihenfolge (Zugangsdaten vor Clients/Switche).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 10:34:31 +02:00
co-authored by Claude Sonnet 5
parent 9a390e740d
commit 197af26d72
3 changed files with 115 additions and 43 deletions
+51 -7
View File
@@ -343,10 +343,13 @@ def _nav_key_visible(key, user):
return False return False
@app.context_processor def _ordered_nav_items():
def inject_nav(): """Komplette Navbar-Struktur (Top-Level + Unterpunkte je Gruppe) in der
if not current_user.is_authenticated: gespeicherten Reihenfolge, OHNE Sichtbarkeitsfilter — Grundlage sowohl
return {} für die Sidebar (dort wird danach noch nach Berechtigung gefiltert) als
auch für den Reihenfolge-Editor auf der Account-Seite (der die
komplette Navbar zeigen soll, unabhängig davon, was der bearbeitende
Admin selbst sehen dürfte — als Admin ohnehin alles)."""
try: try:
stored_order = json.loads(get_setting("nav_order", "") or "[]") stored_order = json.loads(get_setting("nav_order", "") or "[]")
except (ValueError, TypeError): except (ValueError, TypeError):
@@ -354,14 +357,36 @@ def inject_nav():
ordered_keys = [k for k in stored_order if k in NAV_ITEMS_BY_KEY] ordered_keys = [k for k in stored_order if k in NAV_ITEMS_BY_KEY]
ordered_keys += [k for k in DEFAULT_NAV_ORDER if k not in ordered_keys] ordered_keys += [k for k in DEFAULT_NAV_ORDER if k not in ordered_keys]
nav_items_ordered = [] try:
stored_child_order = json.loads(get_setting("nav_child_order", "") or "{}")
except (ValueError, TypeError):
stored_child_order = {}
items = []
for k in ordered_keys: for k in ordered_keys:
item = NAV_ITEMS_BY_KEY[k] item = NAV_ITEMS_BY_KEY[k]
if item.get("children"):
children_by_key = {c["key"]: c for c in item["children"]}
stored = stored_child_order.get(k, [])
child_keys = [ck for ck in stored if ck in children_by_key]
child_keys += [c["key"] for c in item["children"] if c["key"] not in child_keys]
items.append({**item, "children": [children_by_key[ck] for ck in child_keys]})
else:
items.append(item)
return items
@app.context_processor
def inject_nav():
if not current_user.is_authenticated:
return {}
nav_items_ordered = []
for item in _ordered_nav_items():
if item.get("children"): if item.get("children"):
visible_children = [c for c in item["children"] if _nav_key_visible(c["key"], current_user)] visible_children = [c for c in item["children"] if _nav_key_visible(c["key"], current_user)]
if visible_children: if visible_children:
nav_items_ordered.append({**item, "children": visible_children}) nav_items_ordered.append({**item, "children": visible_children})
elif _nav_key_visible(k, current_user): elif _nav_key_visible(item["key"], current_user):
nav_items_ordered.append(item) nav_items_ordered.append(item)
return {"nav_items_ordered": nav_items_ordered} return {"nav_items_ordered": nav_items_ordered}
@@ -880,7 +905,13 @@ def logout():
@app.route("/account") @app.route("/account")
@login_required @login_required
def account(): def account():
return render_template("account.html") # Volle, ungefilterte Navbar-Struktur für den Reihenfolge-Editor (nur
# für Admins sichtbar/relevant) — anders als das automatisch injizierte
# nav_items_ordered (das nach Berechtigung des jeweils eingeloggten
# Benutzers filtert), damit auch Unterpunkte editierbar sind, die der
# bearbeitende Admin selbst nicht als Nicht-Admin sehen würde.
full_nav_items = _ordered_nav_items() if current_user.is_admin else []
return render_template("account.html", full_nav_items=full_nav_items)
@app.route("/profile", methods=["POST"]) @app.route("/profile", methods=["POST"])
@@ -1692,6 +1723,19 @@ def save_nav_order():
order = [k for k in request.form.getlist("nav_order") if k in NAV_ITEMS_BY_KEY] order = [k for k in request.form.getlist("nav_order") if k in NAV_ITEMS_BY_KEY]
order += [k for k in DEFAULT_NAV_ORDER if k not in order] order += [k for k in DEFAULT_NAV_ORDER if k not in order]
set_setting("nav_order", json.dumps(order)) set_setting("nav_order", json.dumps(order))
# Unterpunkt-Reihenfolge je Gruppe — ein Feld "nav_child_order_<group_key>"
# pro Gruppe mit Kindern, Werte in der gewünschten Reihenfolge.
child_order = {}
for group_key, item in NAV_ITEMS_BY_KEY.items():
if not item.get("children"):
continue
valid_child_keys = {c["key"] for c in item["children"]}
submitted = [k for k in request.form.getlist(f"nav_child_order_{group_key}") if k in valid_child_keys]
submitted += [c["key"] for c in item["children"] if c["key"] not in submitted]
child_order[group_key] = submitted
set_setting("nav_child_order", json.dumps(child_order))
log_action("settings.update", "Navbar-Reihenfolge") log_action("settings.update", "Navbar-Reihenfolge")
flash("Navbar-Reihenfolge gespeichert.", "success") flash("Navbar-Reihenfolge gespeichert.", "success")
return redirect(url_for("account")) return redirect(url_for("account"))
+12 -3
View File
@@ -217,9 +217,6 @@ button { font-family: inherit; }
margin-bottom: 16px; margin-bottom: 16px;
} }
.nav-order-list li { .nav-order-list li {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px; padding: 8px 12px;
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-soft); border: 1px solid var(--border-soft);
@@ -227,8 +224,20 @@ button { font-family: inherit; }
font-size: 13.5px; font-size: 13.5px;
font-weight: 550; font-weight: 550;
} }
.nav-order-row { display: flex; align-items: center; justify-content: space-between; }
.nav-order-list .nav-order-actions { display: flex; gap: 4px; } .nav-order-list .nav-order-actions { display: flex; gap: 4px; }
.nav-order-list .icon-btn { width: 28px; height: 28px; } .nav-order-list .icon-btn { width: 28px; height: 28px; }
/* Unterpunkte je Gruppe — eingerückt, innerhalb ihrer Gruppe umsortierbar
(moveNavItem() bewegt nur innerhalb der jeweiligen <ul>, mischt also nie
Unterpunkte verschiedener Gruppen). */
.nav-order-sublist {
list-style: none;
display: flex;
flex-direction: column;
gap: 6px;
margin: 10px 0 2px 22px;
}
.nav-order-sublist li { font-weight: 500; font-size: 12.5px; background: var(--bg-elevated); }
.sidebar-footer { .sidebar-footer {
padding: 14px 12px 18px; padding: 14px 12px 18px;
+52 -33
View File
@@ -4,39 +4,6 @@
{% block content %} {% block content %}
{% if current_user.is_admin %}
<div class="card card-pad" style="margin-bottom:24px;">
<div class="section-head" style="margin-bottom:16px;">
<div>
<h2 style="font-size:16px;">Navbar-Reihenfolge</h2>
<div class="hint">Bestimmt, in welcher Reihenfolge die Menüpunkte in der Sidebar erscheinen. Jeder Benutzer sieht davon nur, wofür er auch berechtigt ist.</div>
</div>
</div>
<form method="post" action="{{ url_for('save_nav_order') }}" id="navOrderForm">
<ul class="nav-order-list" id="navOrderList">
{% for item in nav_items_ordered %}
<li data-key="{{ item.key }}">
<span>{{ item.label }}</span>
<div class="nav-order-actions">
<button type="button" class="icon-btn" title="Nach oben" onclick="moveNavItem(this,-1)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
</button>
<button type="button" class="icon-btn" title="Nach unten" onclick="moveNavItem(this,1)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
</button>
</div>
<input type="hidden" name="nav_order" value="{{ item.key }}">
</li>
{% endfor %}
</ul>
<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>
Reihenfolge speichern
</button>
</form>
</div>
{% endif %}
<div class="settings-grid"> <div class="settings-grid">
<div class="card card-pad"> <div class="card card-pad">
@@ -84,6 +51,58 @@
</div> </div>
</div> </div>
{% if current_user.is_admin %}
{% macro nav_order_buttons() %}
<div class="nav-order-actions">
<button type="button" class="icon-btn" title="Nach oben" onclick="moveNavItem(this,-1)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
</button>
<button type="button" class="icon-btn" title="Nach unten" onclick="moveNavItem(this,1)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
</button>
</div>
{% endmacro %}
<div class="card card-pad" style="margin-top:24px;">
<div class="section-head" style="margin-bottom:16px;">
<div>
<h2 style="font-size:16px;">Navbar-Reihenfolge</h2>
<div class="hint">Bestimmt, in welcher Reihenfolge die Menüpunkte inkl. Unterpunkte in der Sidebar erscheinen. Jeder Benutzer sieht davon nur, wofür er auch berechtigt ist.</div>
</div>
</div>
<form method="post" action="{{ url_for('save_nav_order') }}" id="navOrderForm">
<ul class="nav-order-list" id="navOrderList">
{% for item in full_nav_items %}
<li data-key="{{ item.key }}">
<div class="nav-order-row">
<span>{{ item.label }}</span>
{{ nav_order_buttons() }}
</div>
<input type="hidden" name="nav_order" value="{{ item.key }}">
{% if item.children %}
<ul class="nav-order-sublist">
{% for child in item.children %}
<li data-key="{{ child.key }}">
<div class="nav-order-row">
<span>{{ child.label }}</span>
{{ nav_order_buttons() }}
</div>
<input type="hidden" name="nav_child_order_{{ item.key }}" value="{{ child.key }}">
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
<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>
Reihenfolge speichern
</button>
</form>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}