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 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 # --------------------------------------------------------------------------- # 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.") with st.expander("Neuen Nutzer anlegen"): new_u = st.text_input("Neuer Username", key="new_u") new_fname = st.text_input("Vorname", key="new_fname") new_lname = st.text_input("Nachname", key="new_lname") new_email = st.text_input("E-Mail", key="new_email") new_p = st.text_input("Neues Passwort", type="password", key="new_p") new_role = st.selectbox("Rolle", ["user", "admin"], key="new_role") if st.button("Anlegen"): if new_u and new_p: 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.") st.subheader("Dein Bereich") 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. 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.logo("app/images/GMN_Logo_neu_rgb.png",size="small") # st.markdown("*Controlling*") st.write(f"**{fullname}** ({role})") col1, col2 = st.columns(2) with col1: if st.button("Logout", use_container_width=True): authenticator.logout("Logout", "unrendered") st.rerun() #authenticator.logout("Logout", "sidebar") with col2: # st.page_link("pages/home.py", label="Home", icon="🏠") if st.button("🏠 Home", use_container_width=True): st.session_state["selected_dashboard_code"] = "home" # 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", ) # --- 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) 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) if renderer is None: st.error(f"Kein Dashboard-Modul für '{selected_code}' gefunden.") return # 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()