Add navigation in sidebar
This commit is contained in:
412
app/main.py
412
app/main.py
@@ -1,14 +1,62 @@
|
||||
import importlib
|
||||
|
||||
import streamlit as st
|
||||
import yaml
|
||||
from yaml.loader import SafeLoader
|
||||
import streamlit_authenticator as stauth
|
||||
from auth import load_credentials_from_db, get_role_for_user, create_user, needs_password_change, update_password
|
||||
from streamlit_authenticator.utilities.exceptions import LoginError
|
||||
|
||||
import pandas as pd
|
||||
from contextlib import closing
|
||||
|
||||
from auth import (
|
||||
load_credentials_from_db,
|
||||
get_role_for_user,
|
||||
create_user,
|
||||
needs_password_change,
|
||||
update_password,
|
||||
get_fullname_for_user,
|
||||
)
|
||||
from version import __version__
|
||||
from db import get_conn # an deine Struktur anpassen
|
||||
|
||||
|
||||
def content_for(username: str, role: str):
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dashboards aus der DB laden
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def load_dashboards_df() -> pd.DataFrame:
|
||||
"""Lädt alle aktiven Dashboards aus der DB als DataFrame."""
|
||||
with closing(get_conn()) as conn:
|
||||
df = pd.read_sql_query(
|
||||
"""
|
||||
SELECT
|
||||
d.dash_id,
|
||||
d.dash_text as code,
|
||||
d.dash_text as title,
|
||||
g.group_text as grp
|
||||
from
|
||||
dashboards d
|
||||
left join groups g
|
||||
on d.group_id = g.group_id
|
||||
where
|
||||
d.active = 1
|
||||
order by
|
||||
d.dash_id
|
||||
""",
|
||||
conn,
|
||||
)
|
||||
return df
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Home-Dashboard (als Funktion, kein eigenes .py nötig)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def home_dashboard(username: str, role: str):
|
||||
st.header("Controlling-Portal")
|
||||
st.info(f"Willkommen, {username}!")
|
||||
|
||||
if role == "admin":
|
||||
st.subheader("Admin-Bereich")
|
||||
st.write("Nur Admins sehen das hier.")
|
||||
@@ -23,8 +71,17 @@ def content_for(username: str, role: str):
|
||||
|
||||
if st.button("Anlegen"):
|
||||
if new_u and new_p:
|
||||
ok = create_user(new_u.strip(), new_p, new_role, new_email, new_fname, new_lname)
|
||||
st.success("Nutzer angelegt.") if ok else st.error("Username bereits vorhanden oder Fehler.")
|
||||
ok = create_user(
|
||||
new_u.strip(),
|
||||
new_p,
|
||||
new_role,
|
||||
new_email.strip() or None,
|
||||
new_fname.strip() or None,
|
||||
new_lname.strip() or None,
|
||||
)
|
||||
st.success("Nutzer angelegt.") if ok else st.error(
|
||||
"Username bereits vorhanden oder Fehler."
|
||||
)
|
||||
else:
|
||||
st.warning("Bitte Username und Passwort eingeben.")
|
||||
|
||||
@@ -32,21 +89,98 @@ def content_for(username: str, role: str):
|
||||
st.write(f"Personalisierter Content für **{username}**.")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dashboard-Resolver: code -> render-Funktion
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def get_dashboard_renderer(code: str):
|
||||
"""
|
||||
Liefert die passende Render-Funktion zum Dashboard-Code.
|
||||
Konvention:
|
||||
- Home: interne Funktion home_dashboard(username, role)
|
||||
- andere: Modul dashboards.<code> mit Funktion render(username, role)
|
||||
"""
|
||||
if code == "home":
|
||||
return home_dashboard
|
||||
|
||||
module_name = f"dashboards.{code}"
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
|
||||
# Wir erwarten eine Funktion render(username, role)
|
||||
return getattr(module, "render", None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sidebar: Home + Suche + DB-Dashboards (Gruppen-Expander)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_sidebar(authenticator, username: str, df_dashboards: pd.DataFrame):
|
||||
role = get_role_for_user(username)
|
||||
fullname = get_fullname_for_user(username)
|
||||
|
||||
with st.sidebar:
|
||||
st.write(f"**{fullname}** ({role})")
|
||||
authenticator.logout("Logout", "sidebar")
|
||||
st.divider()
|
||||
st.markdown("### Dashboards")
|
||||
st.divider()
|
||||
|
||||
# Default-Auswahl
|
||||
if "selected_dashboard_code" not in st.session_state:
|
||||
st.session_state["selected_dashboard_code"] = "home"
|
||||
|
||||
# Home ist immer da
|
||||
if st.button("🏠 Home", use_container_width=True):
|
||||
st.session_state["selected_dashboard_code"] = "home"
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# Suchfeld für DB-Dashboards
|
||||
query = st.text_input("Suche", placeholder="Dashboard suchen …")
|
||||
|
||||
filtered = df_dashboards.copy()
|
||||
|
||||
if query:
|
||||
q = query.lower()
|
||||
filtered = filtered[
|
||||
filtered["title"].str.lower().str.contains(q, na=False)
|
||||
| filtered["grp"].str.lower().str.contains(q, na=False)
|
||||
| filtered["description"].fillna("").str.lower().str.contains(q, na=False)
|
||||
]
|
||||
|
||||
if filtered.empty:
|
||||
st.info("Keine Dashboards zur Suche gefunden.")
|
||||
return
|
||||
|
||||
# Gruppen -> Expander, darin Buttons für Dashboards
|
||||
for grp, grp_df in filtered.groupby("grp"):
|
||||
with st.expander(grp, expanded=False):
|
||||
for _, row in grp_df.iterrows():
|
||||
code = row["code"]
|
||||
title = row["title"]
|
||||
|
||||
if st.button(title, key=f"dash_btn_{code}", use_container_width=True):
|
||||
st.session_state["selected_dashboard_code"] = code
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Login + App-Shell
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
st.set_page_config(
|
||||
page_title=f"Co-App Start - V{__version__}",
|
||||
page_icon="🔒",
|
||||
layout="centered",
|
||||
layout="wide",
|
||||
)
|
||||
|
||||
# DB-Struktur sicherstellen
|
||||
# init_auth_db()
|
||||
|
||||
# --- Config laden (Cookie, etc.) ---
|
||||
# --- Authenticator vorbereiten ---
|
||||
with open("config/auth.yaml", "r", encoding="utf-8") as f:
|
||||
base_config = yaml.load(f, Loader=SafeLoader)
|
||||
|
||||
# --- Credentials dynamisch aus DB laden ---
|
||||
db_creds = load_credentials_from_db()
|
||||
base_config["credentials"] = db_creds
|
||||
|
||||
@@ -55,25 +189,20 @@ def main():
|
||||
base_config["cookie"]["name"],
|
||||
base_config["cookie"]["key"],
|
||||
base_config["cookie"]["expiry_days"],
|
||||
#base_config.get("preauthorized", {}),
|
||||
)
|
||||
|
||||
#name, auth_status, username = authenticator.login(location="main", key="Login")
|
||||
# login_result = authenticator.login(location="main", key="Login")
|
||||
|
||||
# if login_result is None:
|
||||
# st.error("Login-Initialisierung fehlgeschlagen (keine gültigen Credentials?).")
|
||||
# return
|
||||
|
||||
# name, auth_status, username = login_result
|
||||
authenticator.login(location="main", key="Login")
|
||||
# --- Login-View zeichnen ---
|
||||
try:
|
||||
authenticator.login(location="main", key="Login")
|
||||
except LoginError:
|
||||
authenticator.logout("ForceLogout", "sidebar")
|
||||
st.error("Sitzung ungültig. Bitte neu einloggen.")
|
||||
return
|
||||
|
||||
auth_status = st.session_state.get("authentication_status")
|
||||
name = st.session_state.get("name")
|
||||
username = st.session_state.get("username")
|
||||
|
||||
|
||||
|
||||
if auth_status is False:
|
||||
st.error("Login fehlgeschlagen.")
|
||||
return
|
||||
@@ -84,10 +213,10 @@ def main():
|
||||
|
||||
# ---- Ab hier eingeloggt (persistenter Cookie) ----
|
||||
|
||||
# 1) Passwortwechsel erzwingen?
|
||||
if needs_password_change(username):
|
||||
st.warning("Du musst dein Passwort ändern, bevor du die Anwendung nutzen kannst.")
|
||||
|
||||
# Damit das Formular nur einmal pro Run erscheint
|
||||
with st.form("pw_change_form"):
|
||||
pw1 = st.text_input("Neues Passwort", type="password")
|
||||
pw2 = st.text_input("Neues Passwort (Wiederholung)", type="password")
|
||||
@@ -106,20 +235,245 @@ def main():
|
||||
|
||||
update_password(username, pw1, reset_flag=True)
|
||||
st.success("Passwort wurde geändert.")
|
||||
|
||||
# Optional: danach Seite einmal „sauber“ neu laden
|
||||
st.rerun()
|
||||
|
||||
# Solange new_pwd=1 ist, KEINEN weiteren Content anzeigen
|
||||
# solange new_pwd=1: keinen weiteren Inhalt anzeigen
|
||||
return
|
||||
|
||||
# 2) Dashboards aus DB laden (ohne Home)
|
||||
df_dashboards = load_dashboards_df()
|
||||
|
||||
# 3) Sidebar aufbauen (User, Logout, Navigation)
|
||||
build_sidebar(authenticator, username, df_dashboards)
|
||||
|
||||
# 4) Ausgewähltes Dashboard rendern
|
||||
role = get_role_for_user(username)
|
||||
selected_code = st.session_state.get("selected_dashboard_code", "home")
|
||||
renderer = get_dashboard_renderer(selected_code)
|
||||
|
||||
authenticator.logout("Logout", "sidebar")
|
||||
st.sidebar.write(f"Angemeldet als **{name}** ({username}, Rolle: {role})")
|
||||
if renderer is None:
|
||||
st.error(f"Kein Dashboard-Modul für '{selected_code}' gefunden.")
|
||||
return
|
||||
|
||||
content_for(username, role)
|
||||
# Titel setzen
|
||||
if selected_code == "home":
|
||||
title = "Home"
|
||||
else:
|
||||
row = df_dashboards[df_dashboards["code"] == selected_code]
|
||||
title = row["title"].iloc[0] if not row.empty else selected_code
|
||||
|
||||
st.title(title)
|
||||
renderer(username, role)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# import importlib
|
||||
|
||||
# import streamlit as st
|
||||
# import yaml
|
||||
# from yaml.loader import SafeLoader
|
||||
# import streamlit_authenticator as stauth
|
||||
# from streamlit_authenticator.utilities.exceptions import LoginError
|
||||
|
||||
# from auth import (
|
||||
# load_credentials_from_db,
|
||||
# get_role_for_user,
|
||||
# create_user,
|
||||
# needs_password_change,
|
||||
# update_password,
|
||||
# get_fullname_for_user,
|
||||
# )
|
||||
# from version import __version__
|
||||
# # später: from db import get_conn, um Dashboards aus DB zu laden
|
||||
|
||||
|
||||
# # ---------------------------------------------------------------------------
|
||||
# # Home-Dashboard (als Funktion, kein eigenes .py nötig)
|
||||
# # ---------------------------------------------------------------------------
|
||||
|
||||
# def home_dashboard(username: str, role: str):
|
||||
# # st.header("Controlling-Portal")
|
||||
# st.info(f"Willkommen, {username}!")
|
||||
|
||||
|
||||
|
||||
|
||||
# # ---------------------------------------------------------------------------
|
||||
# # Dashboard-Resolver (später: weitere Dashboards dynamisch laden)
|
||||
# # ---------------------------------------------------------------------------
|
||||
|
||||
# def get_dashboard_renderer(code: str):
|
||||
# """Liefert die passende Render-Funktion zum Dashboard-Code."""
|
||||
# if code == "home":
|
||||
# # internes Home-Dashboard
|
||||
# return home_dashboard
|
||||
|
||||
# # Später: Module anhand 'code' aus dashboards-Package laden
|
||||
# # Beispiel: dashboards/sales.py → code = "sales"
|
||||
# try:
|
||||
# module = importlib.import_module(f"dashboards.{code}")
|
||||
# except ModuleNotFoundError:
|
||||
# return None
|
||||
|
||||
# return getattr(module, "render", None)
|
||||
|
||||
|
||||
# # ---------------------------------------------------------------------------
|
||||
# # Sidebar – Navigation (Home + später DB-Dashboards)
|
||||
# # ---------------------------------------------------------------------------
|
||||
|
||||
# def build_sidebar(authenticator, username: str):
|
||||
# role = get_role_for_user(username)
|
||||
# fullname = get_fullname_for_user(username)
|
||||
|
||||
# with st.sidebar:
|
||||
# st.write(f"**{fullname}** ({role})")
|
||||
# authenticator.logout("Logout", "sidebar")
|
||||
# st.divider()
|
||||
|
||||
# # Default-Auswahl
|
||||
# if "selected_dashboard_code" not in st.session_state:
|
||||
# st.session_state["selected_dashboard_code"] = "home"
|
||||
|
||||
# # Home ist immer da
|
||||
# if st.button("🏠 Home", use_container_width=True):
|
||||
# st.session_state["selected_dashboard_code"] = "home"
|
||||
|
||||
# # Platzhalter für Suche + DB-Dashboards:
|
||||
# # hier später: Suchfeld + Expander aus DB-Result (DataFrame)
|
||||
# # z.B.:
|
||||
# # query = st.text_input("Dashboards suchen …")
|
||||
# # df = load_dashboards_df()
|
||||
# # ... filtern, gruppieren, Buttons setzen ...
|
||||
|
||||
|
||||
# # ---------------------------------------------------------------------------
|
||||
# # Login + App-Shell
|
||||
# # ---------------------------------------------------------------------------
|
||||
|
||||
# def main():
|
||||
# st.set_page_config(
|
||||
# page_title=f"Co-App Start - V{__version__}",
|
||||
# page_icon="🔒",
|
||||
# layout="centered",
|
||||
# )
|
||||
|
||||
# # --- Authenticator vorbereiten ---
|
||||
# with open("config/auth.yaml", "r", encoding="utf-8") as f:
|
||||
# base_config = yaml.load(f, Loader=SafeLoader)
|
||||
|
||||
# db_creds = load_credentials_from_db()
|
||||
# base_config["credentials"] = db_creds
|
||||
|
||||
# authenticator = stauth.Authenticate(
|
||||
# base_config["credentials"],
|
||||
# base_config["cookie"]["name"],
|
||||
# base_config["cookie"]["key"],
|
||||
# base_config["cookie"]["expiry_days"],
|
||||
# )
|
||||
|
||||
# # --- Login-View zeichnen ---
|
||||
# try:
|
||||
# authenticator.login(location="main", key="Login")
|
||||
# except LoginError:
|
||||
# authenticator.logout("ForceLogout", "sidebar")
|
||||
# st.error("Sitzung ungültig. Bitte neu einloggen.")
|
||||
# return
|
||||
|
||||
# auth_status = st.session_state.get("authentication_status")
|
||||
# name = st.session_state.get("name")
|
||||
# username = st.session_state.get("username")
|
||||
|
||||
# if auth_status is False:
|
||||
# st.error("Login fehlgeschlagen.")
|
||||
# return
|
||||
|
||||
# if auth_status is None:
|
||||
# st.warning("Bitte Benutzername und Passwort eingeben.")
|
||||
# return
|
||||
|
||||
# # ---- Ab hier eingeloggt (persistenter Cookie) ----
|
||||
|
||||
# # 1) Passwortwechsel erzwingen?
|
||||
# if needs_password_change(username):
|
||||
# st.warning("Du musst dein Passwort ändern, bevor du die Anwendung nutzen kannst.")
|
||||
|
||||
# with st.form("pw_change_form"):
|
||||
# pw1 = st.text_input("Neues Passwort", type="password")
|
||||
# pw2 = st.text_input("Neues Passwort (Wiederholung)", type="password")
|
||||
# submitted = st.form_submit_button("Passwort ändern")
|
||||
|
||||
# if submitted:
|
||||
# if not pw1 or not pw2:
|
||||
# st.error("Bitte beide Passwortfelder ausfüllen.")
|
||||
# return
|
||||
# if pw1 != pw2:
|
||||
# st.error("Passwörter stimmen nicht überein.")
|
||||
# return
|
||||
# if len(pw1) < 8:
|
||||
# st.error("Passwort sollte mindestens 8 Zeichen lang sein.")
|
||||
# return
|
||||
|
||||
# update_password(username, pw1, reset_flag=True)
|
||||
# st.success("Passwort wurde geändert.")
|
||||
# st.rerun()
|
||||
|
||||
# # solange new_pwd=1: keinen weiteren Inhalt anzeigen
|
||||
# return
|
||||
|
||||
# # 2) Sidebar aufbauen (User, Logout, Navigation)
|
||||
# build_sidebar(authenticator, username)
|
||||
|
||||
# # 3) Ausgewähltes Dashboard rendern
|
||||
# role = get_role_for_user(username)
|
||||
# selected_code = st.session_state.get("selected_dashboard_code", "home")
|
||||
# renderer = get_dashboard_renderer(selected_code)
|
||||
|
||||
# if renderer is None:
|
||||
# st.error(f"Kein Dashboard-Modul für '{selected_code}' gefunden.")
|
||||
# return
|
||||
|
||||
# # Titel setzen (Home vs. andere Dashboards)
|
||||
# if selected_code == "home":
|
||||
# title = "Home"
|
||||
# else:
|
||||
# title = selected_code # später kannst du den Titel aus der DB holen
|
||||
|
||||
# st.title(title)
|
||||
# renderer(username, role)
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
|
||||
Reference in New Issue
Block a user