Spaces:
Running
Running
| """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( | |
| """ | |
| <style> | |
| .sync-login-container { | |
| max-width: 420px; | |
| margin: 100px auto 0 auto; | |
| padding: 36px 32px; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #2a1a3e 100%); | |
| border-radius: 16px; | |
| color: #f5f5fa; | |
| text-align: center; | |
| } | |
| .sync-login-title { font-size: 1.9em; margin-bottom: 4px; } | |
| .sync-login-subtitle { opacity: 0.75; margin-bottom: 8px; } | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| col1, col2, col3 = st.columns([1, 2, 1]) | |
| with col2: | |
| st.markdown( | |
| """ | |
| <div class="sync-login-container"> | |
| <div class="sync-login-title">sync_pilot</div> | |
| <div class="sync-login-subtitle">Median Müzik · sync-licensing pilot</div> | |
| </div> | |
| """, | |
| 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() | |