"""HuggingFace Space entrypoint for the sync_pilot dashboard. Wraps the multi-page Streamlit app in ``sync_pilot.dashboard`` with an ``ACCESS_KEY`` gate (matching the rehab-robotics GURMA dashboard's pattern in ``src/dashboard/app.py``). The wrapper *replicates* the navigation block from ``sync_pilot/dashboard/app.py:main`` rather than importing the module — the module calls ``main()`` at top level, which would re-fire ``st.set_page_config`` after the wrapper has already set it. Required Space secrets (set in HF UI): ACCESS_KEY — the login password HF_TOKEN — read token for the private dataset PRIVATE_DATASET_REPO — e.g. ``emresar/gurma-dashboard-private-data`` SYNC_PILOT_DATA_SOURCE — ``hf`` (default in the Dockerfile) """ from __future__ import annotations import hashlib import os import streamlit as st # ``set_page_config`` must be the first Streamlit call — set it before # any other ``st.*`` invocation (including ones that might happen inside # the auth gate's ``show_login`` form). st.set_page_config( page_title="sync_pilot — Median Müzik", page_icon="♪", layout="wide", initial_sidebar_state="expanded", ) # --------------------------------------------------------------------------- # Access control (mirrors src/dashboard/app.py for the rehab dashboard) # --------------------------------------------------------------------------- ACCESS_KEY = os.getenv("ACCESS_KEY", "") IS_HF_SPACE = bool(os.getenv("HF_SPACE")) def _auth_token(key: str, salt: str = "sync_pilot") -> str: """Short deterministic token kept in the URL so refresh survives.""" return hashlib.sha256(f"{salt}_{key}".encode()).hexdigest()[:16] def check_access() -> bool: """Authenticate via session state, URL token, or pass-through locally.""" if not ACCESS_KEY: # Local dev (no key configured) — bypass. On HF Space we refuse # rather than open the dashboard up; treat missing secret as a # misconfiguration the user can fix from the Settings panel. return not IS_HF_SPACE if st.session_state.get("authenticated"): return True if st.query_params.get("auth") == _auth_token(ACCESS_KEY): st.session_state.authenticated = True return True return False def show_login() -> None: st.markdown( """ """, unsafe_allow_html=True, ) col1, col2, col3 = st.columns([1, 2, 1]) with col2: st.markdown( """
sync_pilot
Median Müzik · sync-licensing pilot
""", unsafe_allow_html=True, ) st.markdown("") with st.form("sync_pilot_login_form"): key = st.text_input( "Access Key", type="password", placeholder="Enter your access key", ) submitted = st.form_submit_button("Enter", width="stretch", type="primary") if submitted: if key == ACCESS_KEY: st.session_state.authenticated = True st.query_params["auth"] = _auth_token(ACCESS_KEY) st.rerun() else: st.error("Invalid access key") if not check_access(): show_login() st.stop() # --------------------------------------------------------------------------- # Navigation — mirrors ``sync_pilot/dashboard/app.py:main`` (kept in sync # manually; touching one without the other will show different pages # between local and Space). # --------------------------------------------------------------------------- from sync_pilot.dashboard.styles import apply_global_styles # noqa: E402 from sync_pilot.dashboard.views import ( # noqa: E402 gt_review, overview, taxonomy, tracks, ) apply_global_styles() def _sidebar_chrome() -> None: with st.sidebar: st.markdown("---") st.markdown("### sync_pilot") st.caption("Median Müzik · sync-licensing pilot") source = os.getenv("SYNC_PILOT_DATA_SOURCE", "local") st.caption(f"data source: `{source}`") if ACCESS_KEY and st.session_state.get("authenticated"): if st.button("Logout", width="stretch"): st.session_state.authenticated = False st.query_params.pop("auth", None) st.rerun() nav = st.navigation( [ st.Page( overview.render, title="Overview", icon=":material/dashboard:", url_path="overview", default=True, ), st.Page( taxonomy.render, title="Taxonomy", icon=":material/category:", url_path="taxonomy", ), st.Page( tracks.render, title="Track explorer", icon=":material/library_music:", url_path="tracks", ), st.Page( gt_review.render, title="GT review", icon=":material/fact_check:", url_path="gt-review", ), ], position="sidebar", ) _sidebar_chrome() nav.run()