Support demo users
Some checks failed
Run Tests / run-tests (push) Failing after 1m2s

This commit is contained in:
2026-05-01 17:41:42 +02:00
parent a88d1b4e79
commit 0632899f7a
11 changed files with 169 additions and 134 deletions

View File

@@ -2,19 +2,17 @@ import streamlit as st
import queries.crud as crud
import themes as th
is_login_enabled = hasattr(st, 'user') and hasattr(st.user, 'is_logged_in')
is_logged_in = is_login_enabled and st.user.is_logged_in
from user import is_logged_in, is_login_enabled
st.title("Settings")
if hasattr(st.session_state, 'user_name'):
if hasattr(st.session_state, 'user_name') and st.session_state.user_name:
st.markdown(f"Currently logged in as **{st.session_state.user_name}**")
st.header("Theme")
themes = st.session_state.themes
with st.container(horizontal=True, width="stretch"):
for theme in ["light", "dark"]:
for theme in themes.keys():
if st.button(label=themes[theme]["button_face_label"],
icon = themes[theme]["button_face_icon"],
disabled = (theme == st.session_state.current_theme),
@@ -61,8 +59,9 @@ with st.container(key="settings-color-selector"):
""", width=400)
st.header("Actions")
with st.container():
if is_logged_in:
if st.button("Logout", icon=":material/logout:", width="stretch"):
st.logout()
if is_login_enabled():
st.header("Actions")
with st.container():
if is_logged_in():
if st.button("Logout", icon=":material/logout:", width="stretch"):
st.logout()

View File

@@ -2,9 +2,10 @@ from os import getenv
import streamlit as st
from sqlalchemy.sql import text
from streamlit.connections import BaseConnection
from streamlit.connections import SQLConnection
def connection() -> BaseConnection:
def connection() -> SQLConnection:
_connection = st.connection("sql", url=getenv('DATABASE_URL'), ttl=0, autocommit=True)
with _connection.session as configured_session:
configured_session.execute(text('PRAGMA foreign_keys=ON'))

View File

@@ -139,6 +139,7 @@ def set_theme(theme:str):
logger.error(e)
session.rollback()
def get_theme() -> str:
user_id = int(st.session_state.user_id)
try:

View File

@@ -1,6 +1,7 @@
import logging
import streamlit as st
from pandas import DataFrame
from sqlalchemy.sql import text
from streamlit.user_info import UserInfoProxy
@@ -11,11 +12,9 @@ logger = logging.getLogger(__name__)
def find_user_by_oidc_id(oidc_user_id):
return connection().query('SELECT * FROM users WHERE oidc_user_id = :id', params={'id': oidc_user_id})
def find_user_by_email(email):
return connection().query('SELECT * FROM users WHERE email = :email', params={'email': email})
def find_default_user():
return find_user_by_email('default')
@@ -30,40 +29,12 @@ def update_default_user(email, name, oidc_user_id):
raise e
def create_user(email, name, oidc_user_id):
def create_user(email, name, oidc_user_id) -> DataFrame:
with connection().session as session:
try:
logger.info("Creating new user %s", email)
query = text('INSERT INTO users (email, name, oidc_user_id) VALUES (:email, :name, :user_id)')
session.execute(query, {'email': email, 'name': name, 'user_id': oidc_user_id})
return connection().query('SELECT * FROM users WHERE oidc_user_id = :id', params={'id': oidc_user_id})
query = text('INSERT INTO users (email, name, oidc_user_id) VALUES (:email, :name, :user_id) RETURNING *')
return DataFrame(session.execute(query, {'email': email, 'name': name, 'user_id': oidc_user_id}))
except Exception as e:
session.rollback()
raise e
def set_user_in_session(user: UserInfoProxy):
email = user.email if hasattr(user, "email") else None
user_id = user.sub if hasattr(user, "sub") else None
name = user.name if hasattr(user, "name") else None
user_entity = find_user_by_oidc_id(user_id) if user_id else st.dataframe()
if user_entity.empty:
user_entity = find_user_by_email(email) if email else st.dataframe()
if user_entity.empty:
user_entity = find_default_user()
if user_entity.empty and email and name and user_id:
user_entity = create_user(email, name, user_id)
elif name:
update_default_user(email, name, user_id)
user_entity = find_user_by_oidc_id(user_id)
if not user_entity.empty:
st.session_state.user_id = user_entity["id"][0]
st.session_state.user_name = user_entity["name"][0]
st.session_state.user_email = user_entity["email"][0]
st.session_state.user_external_id = user_entity["oidc_user_id"][0]
st.session_state.current_theme = user_entity["theme"][0]
else:
logger.warn("No active user found!")

View File

@@ -1,10 +1,11 @@
import streamlit as st
from streamlit import dialog
import queries.user as user_queries
import random
from logger import init_logger
from styles import init_styles
from user import init_user, is_login_enabled, is_logged_in
from user import init_user, is_login_enabled, is_logged_in, init_demo_user
from themes import init_themes
init_logger()
@@ -12,18 +13,21 @@ init_user()
init_styles()
init_themes()
if is_login_enabled and not is_logged_in:
with st.container(width="stretch", height="stretch", horizontal_alignment="center"):
st.title("Daily Counter", width="stretch", text_alignment="center")
st.text("Please log in to use this app", width="stretch", text_alignment="center")
st.space()
if st.button("Log in"):
st.login()
else:
if not is_login_enabled() or is_logged_in():
counters = st.Page("pages/counters.py", title="Counters", icon=":material/update:")
stats = st.Page("pages/stats.py", title="Statistics", icon=":material/chart_data:")
settings = st.Page("pages/settings.py", title=" ", icon=":material/menu:")
pages = [counters, stats, settings]
pg = st.navigation(position="top", pages=pages)
pg.run()
else:
with st.container(width="stretch", height="stretch", horizontal_alignment="center"):
st.title("Daily Counter", width="stretch", text_alignment="center")
st.text("Please log in to use this app", width="stretch", text_alignment="center")
st.space()
if st.button("Log in", width="stretch",icon=":material/login:"):
st.login()
if st.button("Demo", width="stretch", icon=":material/account_box:"):
init_demo_user()
st.rerun()

View File

@@ -1,6 +1,6 @@
import streamlit as st
import queries
from queries import crud
from user import is_logged_in
def _load_css(filepath):
with open(filepath) as file:
@@ -8,16 +8,17 @@ def _load_css(filepath):
def _load_color_selector_styles():
colors = crud.get_colors()
for idx, c in enumerate(colors.keys()):
css_color = '#' + colors[c][0]
st.html(f"""
<style>
.st-key-new_counter_color_selector label:has(> input[value='{idx}']) {{
background-color: {css_color};
}}
</style>
""")
if is_logged_in():
colors = crud.get_colors()
for idx, c in enumerate(colors.keys()):
css_color = '#' + colors[c][0]
st.html(f"""
<style>
.st-key-new_counter_color_selector label:has(> input[value='{idx}']) {{
background-color: {css_color};
}}
</style>
""")
def init_styles():

View File

@@ -1,28 +1,28 @@
import streamlit as st
from queries import crud
from user import is_logged_in, is_login_enabled
def init_themes():
if 'themes' not in st.session_state:
st.session_state.themes = {
"dark": {
"theme.base": "dark",
"theme.backgroundColor": "black",
"theme.primaryColor": "#c98bdb",
"theme.secondaryBackgroundColor": "#5591f5",
"theme.textColor": "white",
"button_face_label": "Dark",
"button_face_icon": ":material/dark_mode:"
},
"light": {
"theme.base": "light",
"theme.backgroundColor": "white",
"theme.primaryColor": "#5591f5",
"theme.secondaryBackgroundColor": "#82E1D7",
"theme.textColor": "#0a1464",
"button_face_label": "Light",
"button_face_icon": ":material/light_mode:"
},
"dark": {
"theme.base": "dark",
"theme.backgroundColor": "black",
"theme.primaryColor": "#c98bdb",
"theme.textColor": "white",
"button_face_label": "Dark",
"button_face_icon": ":material/dark_mode:"
}
}
if 'current_theme' not in st.session_state:
@@ -30,7 +30,10 @@ def init_themes():
change_theme('light')
def change_theme(theme):
crud.set_theme(theme)
if is_logged_in():
crud.set_theme(theme)
st.session_state.current_theme = theme
for key, val in st.session_state.themes[theme].items():
if key.startswith("theme"):
st._config.set_option(key, val)

View File

@@ -1,8 +1,60 @@
import logging
import random
import queries.user as user_queries
import streamlit as st
is_login_enabled = hasattr(st, 'user') and hasattr(st.user, 'is_logged_in')
is_logged_in = is_login_enabled and st.user.is_logged_in
from pandas import DataFrame
from streamlit.user_info import UserInfoProxy
logger = logging.getLogger(__name__)
def is_login_enabled() -> bool:
return hasattr(st, 'user') and hasattr(st.user, 'is_logged_in')
def is_demo_user() -> bool:
return hasattr(st.session_state, 'user_is_demo') and st.session_state.user_is_demo
def is_logged_in() -> bool:
return not is_login_enabled() or is_demo_user() or (is_login_enabled() and st.user.is_logged_in)
def init_user():
user_queries.set_user_in_session(st.user)
if not is_demo_user():
set_user_in_session(st.user)
def set_user_in_session(user: UserInfoProxy):
email = user.email if hasattr(user, "email") else None
user_id = user.sub if hasattr(user, "sub") else None
name = user.name if hasattr(user, "name") else None
user_entity = user_queries.find_user_by_oidc_id(user_id) if user_id else DataFrame()
if user_entity.empty:
user_entity = user_queries.find_user_by_email(email) if email else DataFrame()
if user_entity.empty:
user_entity = user_queries.find_default_user()
if user_entity.empty and email and name and user_id:
user_entity = user_queries.create_user(email, name, user_id)
elif name:
user_queries.update_default_user(email, name, user_id)
user_entity = user_queries.find_user_by_oidc_id(user_id)
if not user_entity.empty:
st.session_state.user_id = int(user_entity["id"][0])
st.session_state.user_name = user_entity["name"][0]
st.session_state.user_email = user_entity["email"][0]
st.session_state.user_external_id = user_entity["oidc_user_id"][0]
st.session_state.current_theme = user_entity["theme"][0]
st.session_state.user_is_demo = False
else:
logger.warning("No active user found!")
def init_demo_user():
demo_id = ''.join(random.choice('0123456789abcdefghijklmnopqrstuvwxyz') for i in range(6))
demo_user = user_queries.create_user(f"demo+{demo_id}@internal", f"Demo user {demo_id}", None)
st.session_state.user_id = int(demo_user["id"][0])
st.session_state.user_name = demo_user["name"][0]
st.session_state.user_email = demo_user["email"][0]
st.session_state.user_external_id = demo_user["oidc_user_id"][0]
st.session_state.current_theme = demo_user["theme"][0]
st.session_state.user_is_demo = True