495 lines
15 KiB
Python
495 lines
15 KiB
Python
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()
|