Skip to content

Commit 9cc2944

Browse files
committed
Add authentification for accessing detailed map
1 parent db8c818 commit 9cc2944

File tree

5 files changed

+127
-7
lines changed

5 files changed

+127
-7
lines changed

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ services:
88
#image: ghcr.io/ninanor/dashboard-dashboard:main@sha-9a71d5a
99
environment:
1010
- AUTH_USERNAME=${AUTH_USERNAME}
11-
- AUTH_PASSWORD=${AUTH_PASSWORD}
11+
- AUTH_PASSWORD=${AUTH_PASSWORD} # Also used for detailed map access
1212
- BASE_DATA_DIR=${BASE_DATA_DIR}
1313
reverseproxy:
1414
build: proxy

src/components/auth.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""
2+
Authentication component for detailed map access.
3+
"""
4+
5+
import os
6+
import streamlit as st
7+
from config.settings import DETAILED_MAP_PASSWORD
8+
9+
10+
def get_detailed_map_password() -> str:
11+
"""Get the detailed map password from environment variable or config."""
12+
return os.environ.get("AUTH_PASSWORD", DETAILED_MAP_PASSWORD)
13+
14+
15+
def check_detailed_map_access() -> bool:
16+
"""
17+
Check if the user has access to the detailed map.
18+
Returns True if authorized, False otherwise.
19+
"""
20+
# Initialize session state for detailed map access
21+
if "detailed_map_authorized" not in st.session_state:
22+
st.session_state.detailed_map_authorized = False
23+
24+
return st.session_state.detailed_map_authorized
25+
26+
27+
def render_detailed_map_auth() -> bool:
28+
"""
29+
Render the detailed map authentication interface.
30+
Returns True if user is authorized for detailed map access.
31+
"""
32+
# Check if already authorized
33+
if check_detailed_map_access():
34+
return True
35+
36+
# Show authentication interface
37+
with st.expander("🔐 Detailed Map Access", expanded=False):
38+
st.markdown("""
39+
**Project Team Access**: Enter your dashboard password to access detailed device locations.
40+
41+
⚠️ **Note**: This will show exact device coordinates for project management purposes.
42+
""")
43+
44+
password = st.text_input(
45+
"Password",
46+
type="password",
47+
help="Enter your dashboard password to unlock detailed map view",
48+
key="detailed_map_password"
49+
)
50+
51+
col1, col2 = st.columns([1, 3])
52+
53+
with col1:
54+
if st.button("🔓 Unlock Detailed Map", type="primary"):
55+
if password == get_detailed_map_password():
56+
st.session_state.detailed_map_authorized = True
57+
st.success("✅ Access granted! Detailed map is now available.")
58+
st.rerun()
59+
else:
60+
st.error("❌ Invalid password")
61+
62+
with col2:
63+
if st.session_state.detailed_map_authorized:
64+
if st.button("🔒 Lock Detailed Map"):
65+
st.session_state.detailed_map_authorized = False
66+
st.info("🔒 Detailed map access revoked")
67+
st.rerun()
68+
69+
return check_detailed_map_access()
70+
71+
72+
def get_map_zoom_level() -> int:
73+
"""
74+
Get the appropriate zoom level based on user authorization.
75+
Returns detailed zoom level if authorized, otherwise privacy-protected level.
76+
"""
77+
from config.settings import MAX_ZOOM_LEVEL, DETAILED_MAP_MAX_ZOOM
78+
79+
if check_detailed_map_access():
80+
return DETAILED_MAP_MAX_ZOOM
81+
else:
82+
return MAX_ZOOM_LEVEL
83+
84+
85+
def get_map_access_status() -> dict:
86+
"""
87+
Get the current map access status and settings.
88+
Returns a dictionary with access information.
89+
"""
90+
from config.settings import MAX_ZOOM_LEVEL, DETAILED_MAP_MAX_ZOOM
91+
92+
is_authorized = check_detailed_map_access()
93+
94+
return {
95+
"is_authorized": is_authorized,
96+
"current_max_zoom": DETAILED_MAP_MAX_ZOOM if is_authorized else MAX_ZOOM_LEVEL,
97+
"access_level": "Detailed (Project Team)" if is_authorized else "Privacy Protected (Public)",
98+
"zoom_description": f"Up to level {DETAILED_MAP_MAX_ZOOM}" if is_authorized else f"Limited to level {MAX_ZOOM_LEVEL}"
99+
}

src/components/map_viz.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
DEFAULT_ZOOM,
1313
MAP_HEIGHT,
1414
MAP_WIDTH,
15-
MAX_ZOOM_LEVEL,
1615
MIN_ZOOM_LEVEL
1716
)
17+
from components.auth import get_map_zoom_level
1818

1919

2020
def render_device_map(
@@ -27,8 +27,8 @@ def render_device_map(
2727
st.warning("⚠️ No site information available")
2828
return None
2929

30-
# Use provided settings or fall back to config defaults
31-
use_max_zoom = max_zoom if max_zoom is not None else MAX_ZOOM_LEVEL
30+
# Use provided settings or get dynamic zoom level based on user authorization
31+
use_max_zoom = max_zoom if max_zoom is not None else get_map_zoom_level()
3232

3333
# Create map centered on device locations
3434
center_lat = site_info["Latitude"].mean()

src/config/settings.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@
2525
MAP_WIDTH = 1200
2626

2727
# Privacy protection settings
28-
MAX_ZOOM_LEVEL = 7 # Maximum zoom level (prevents very detailed viewing) - hardcoded for privacy
28+
MAX_ZOOM_LEVEL = 7 # Maximum zoom level for public access (prevents very detailed viewing)
2929
MIN_ZOOM_LEVEL = 3 # Minimum zoom level
30+
DETAILED_MAP_MAX_ZOOM = 18 # Maximum zoom level for authorized users (full detail)
31+
32+
# Detailed map access configuration
33+
# Password can be set via AUTH_PASSWORD environment variable (recommended for production)
34+
# This same variable is used for basic authentication in the reverse proxy
35+
DETAILED_MAP_PASSWORD = "tabmon2025" # Fallback password for detailed map access
3036

3137
# Chart settings
3238
HEATMAP_COLORSCALE = "Viridis"

src/map_dashboard.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from components.metrics import render_status_metrics
1515
from components.sidebar import render_complete_sidebar
1616
from components.tables import render_status_table, render_summary_table
17+
from components.auth import render_detailed_map_auth, get_map_access_status
1718
from components.ui_styles import (
1819
load_custom_css,
1920
render_info_section_header,
@@ -84,8 +85,22 @@ def render_map_tab(device_data: pd.DataFrame, data_service: DataService):
8485
"""Render the interactive map tab."""
8586
st.markdown("### Device Locations and Status")
8687

87-
# Privacy notice
88-
st.info("🔒 **Privacy Protection**: Map zoom is limited to protect sensitive device location details.")
88+
# Authentication interface for detailed map access
89+
is_authorized = render_detailed_map_auth()
90+
91+
# Show current access status
92+
access_status = get_map_access_status()
93+
94+
if is_authorized:
95+
st.success(
96+
f"🔓 **{access_status['access_level']}** - "
97+
f"Zoom {access_status['zoom_description']} available"
98+
)
99+
else:
100+
st.info(
101+
f"🔒 **{access_status['access_level']}** - "
102+
f"Zoom {access_status['zoom_description']} for privacy protection"
103+
)
89104

90105
# Filters for map view
91106
filtered_data, active_filters = render_complete_filters(

0 commit comments

Comments
 (0)