Files
co_app/app/main_v1.py
2025-12-03 22:03:15 +01:00

495 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.<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.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()