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:
+51
-7
@@ -343,10 +343,13 @@ def _nav_key_visible(key, user):
|
||||
return False
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_nav():
|
||||
if not current_user.is_authenticated:
|
||||
return {}
|
||||
def _ordered_nav_items():
|
||||
"""Komplette Navbar-Struktur (Top-Level + Unterpunkte je Gruppe) in der
|
||||
gespeicherten Reihenfolge, OHNE Sichtbarkeitsfilter — Grundlage sowohl
|
||||
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:
|
||||
stored_order = json.loads(get_setting("nav_order", "") or "[]")
|
||||
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 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:
|
||||
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"):
|
||||
visible_children = [c for c in item["children"] if _nav_key_visible(c["key"], current_user)]
|
||||
if 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)
|
||||
return {"nav_items_ordered": nav_items_ordered}
|
||||
|
||||
@@ -880,7 +905,13 @@ def logout():
|
||||
@app.route("/account")
|
||||
@login_required
|
||||
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"])
|
||||
@@ -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 DEFAULT_NAV_ORDER if k not in 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")
|
||||
flash("Navbar-Reihenfolge gespeichert.", "success")
|
||||
return redirect(url_for("account"))
|
||||
|
||||
@@ -217,9 +217,6 @@ button { font-family: inherit; }
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.nav-order-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-soft);
|
||||
@@ -227,8 +224,20 @@ button { font-family: inherit; }
|
||||
font-size: 13.5px;
|
||||
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 .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 {
|
||||
padding: 14px 12px 18px;
|
||||
|
||||
@@ -4,39 +4,6 @@
|
||||
|
||||
{% 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="card card-pad">
|
||||
@@ -84,6 +51,58 @@
|
||||
</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 %}
|
||||
|
||||
{% block scripts %}
|
||||
|
||||
Reference in New Issue
Block a user