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
|
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"))
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user