diff --git a/app/auth.py b/app/auth.py index de09f6b..7a82f02 100644 --- a/app/auth.py +++ b/app/auth.py @@ -67,7 +67,7 @@ def verify_user(username: str, password: str): def get_role_for_user(username: str) -> str: with closing(get_conn()) as conn: row = conn.execute( - "SELECT role_id FROM users WHERE username = ?", + "SELECT r.role_text from users u left join roles r on u.role_id = r.role_id WHERE username = ?", (username,), ).fetchone() return row[0] if row else "user" diff --git a/app/db.py b/app/db.py index 5c7b5c2..abadd2f 100644 --- a/app/db.py +++ b/app/db.py @@ -1,6 +1,7 @@ import sqlite3 from pathlib import Path import bcrypt +import pandas as pd BASE_DIR = Path(__file__).resolve().parent.parent DB_PATH = BASE_DIR / "data" / "app.db" @@ -10,3 +11,7 @@ def get_conn(): # check_same_thread=False, damit Streamlit mehrere Threads nutzen kann return sqlite3.connect(DB_PATH, check_same_thread=False) +def get_list(sql, params=None): + conn = get_conn() + df = pd.read_sql_query(sql, conn, params=params) + return df \ No newline at end of file diff --git a/app/pages/Benutzer.py b/app/pages/user.py similarity index 98% rename from app/pages/Benutzer.py rename to app/pages/user.py index 450f099..104caa5 100644 --- a/app/pages/Benutzer.py +++ b/app/pages/user.py @@ -11,7 +11,7 @@ authenticator = require_login() st.session_state["authenticator"] = authenticator username = st.session_state.get("username") -build_sidebar() +# build_sidebar() st.title("Benutzerverwaltung") diff --git a/app/ui/sidebar.py b/app/ui/sidebar.py index 5ad6f03..92b09fa 100644 --- a/app/ui/sidebar.py +++ b/app/ui/sidebar.py @@ -1,5 +1,9 @@ +from contextlib import closing import streamlit as st from auth import get_fullname_for_user, get_role_for_user +from db import get_conn, get_list +import sqlite3 + def build_sidebar(): @@ -15,6 +19,53 @@ def build_sidebar(): role = get_role_for_user(username) fullname = get_fullname_for_user(username) + if role == "admin": + sql = """ + select + g.group_id, + g.group_text, + d.dash_id, + d.dash_text, + d.page_file, + d.dash_type + from + groups g + left join dashboards d + on g.group_id = d.group_id + where + g.active = 1 + and d.active = 1 + """ + else: + sql = """ + SELECT + d.group_id, + g.group_text, + p.dash_id, + d.dash_text, + d.page_file, + d.dash_type + FROM + users u + left join permissions p + on u.role_id = p.role_id + left join dashboards d + on p.dash_id = d.dash_id + left join groups g + on d.group_id = g.group_id + where + u.active = 1 + and g.active = 1 + and d.active = 1 + and p.active = 1 + and u.username = ? + order by + g.order_no, + d.order_no + """ + params = (username,) if "?" in sql else None + df = get_list(sql, params) + with st.sidebar: st.logo("app/images/GMN_Logo_neu_rgb.png", size="small") st.write(f"**{fullname}** ({role})") @@ -30,11 +81,79 @@ def build_sidebar(): st.divider() st.markdown("## Menü") - st.page_link("pages/Benutzer.py", label="Benutzer anlegen") + # for group_text, df_group in df.groupby("group_text"): + # with st.expander(group_text, expanded=False): + # for _, row in df_group.iterrows(): + # dash_type = row.get("dash_type") + # page_file = row.get("page_file") + # label = row.get("dash_text", "") + # print(dash_type, page_file, label) + # # 1) echte Streamlit-Page + # if dash_type == "page" and isinstance(page_file, str) and page_file.strip(): + # st.page_link( + # page_file, # z.B. "pages/umsatz.py" + # label=label, + # ) + + # # 2) externer Link (oder interner HTTP-Link) + # elif dash_type == "url" and isinstance(page_file, str) and page_file.strip(): + # st.markdown( + # f"[{label}]({page_file})", + # unsafe_allow_html=False, + # ) + + # # 3) Platzhalter / noch nicht implementiert + # else: + # st.write(f"▫️ {label} (in Vorbereitung)") + # --- Suchfeld --- + query = st.text_input("Menü-Suche", "", placeholder="z.B. Umsatz, Kosten, User ...") + query = query.strip() + # Aktive Seite ermitteln (für Expander-Status / Highlight) + current_page = st.session_state.get("_page_path") + # --- DF filtern, falls Suchbegriff gesetzt --- + if query: + mask = ( + df["dash_text"].str.contains(query, case=False, na=False) + | df["group_text"].str.contains(query, case=False, na=False) + ) + df_view = df[mask].copy() + else: + df_view = df + + if df_view.empty: + st.info("Keine Einträge zum Suchbegriff gefunden.") + return + + # --- Gruppiert durchlaufen --- + for group_text, df_group in df_view.groupby("group_text"): + # Expander offen, wenn: + # - aktuelle Seite in dieser Gruppe liegt + group_open = any( + (row["page_file"] == current_page) + for _, row in df_group.iterrows() + ) + + with st.expander(group_text, expanded=(group_open or bool(query))): + for _, row in df_group.iterrows(): + dash_type = row.get("dash_type") + page_file = row.get("page_file") + label = row.get("dash_text", "") + + # Streamlit-Page + if dash_type == "page" and isinstance(page_file, str) and page_file.strip(): + st.page_link(page_file, label=label) + + # Externer Link + elif dash_type == "url" and isinstance(page_file, str) and page_file.strip(): + st.markdown(f"[{label}]({page_file})") + + # Platzhalter / Sonstiges + else: + st.write(f"▫️ {label}") # Damit die leere Sidebar komplett verschwindet nach dem Logout def hide_sidebar_if_logged_out(): diff --git a/data/app.db b/data/app.db index 5382b6f..f5f86da 100644 Binary files a/data/app.db and b/data/app.db differ diff --git a/migrations/20251204_210700_add_col_page_dashboards.sql b/migrations/20251204_210700_add_col_page_dashboards.sql new file mode 100644 index 0000000..3ccd48a --- /dev/null +++ b/migrations/20251204_210700_add_col_page_dashboards.sql @@ -0,0 +1,11 @@ +begin; + +alter table dashboards +add column page_file text; + +alter table dashboards +add column dash_type text; + +INSERT INTO schema_version (version) VALUES ('20251204_210700_add_col_page_dashboards'); + +COMMIT; \ No newline at end of file