ramanna's picture
Upload 30 files
0e39328 verified
import streamlit as st
import streamlit_authenticator as stauth
from pathlib import Path
import sys
import pandas as pd
import subprocess
from datetime import datetime
import os
from huggingface_upload import upload_all_to_huggingface
# Allow imports of project modules
sys.path.insert(0, str(Path(__file__).parent.parent))
from user_management import HuggingFaceUserManager, load_user_config
st.set_page_config(page_title="Admin Panel", layout="wide", page_icon="🛠️")
# CSS
st.markdown("""
<style>
.main .block-container { padding-top: 2rem; max-width: 1200px; }
h2 { color: #e0e0e0 !important; font-weight: 400 !important; font-size: 1.5rem !important; }
</style>
""", unsafe_allow_html=True)
# CONFIG
config, using_hf = load_user_config()
if config is None:
st.error("Authentication configuration not found!")
st.stop()
# AUTH SYSTEM
authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days']
)
try:
authenticator.login('main')
except Exception as e:
st.error(f"Login error: {e}")
name = st.session_state.get("name")
authentication_status = st.session_state.get("authentication_status")
username = st.session_state.get("username")
if authentication_status == False:
st.error('Username/password is incorrect')
st.stop()
if authentication_status == None:
st.warning('Please enter your username and password')
st.stop()
# AUTH VIEW
if authentication_status:
with st.sidebar:
st.markdown("---")
st.markdown(f"**Logged in as:** {name}")
st.markdown(f"**Username:** {username}")
authenticator.logout('Logout', 'sidebar')
ALLOWED_USERNAMES = set(config['credentials']['usernames'].keys())
if username not in ALLOWED_USERNAMES:
st.error(f"User '{username}' is not authorized.")
st.stop()
# HEADER
st.success(f"Welcome, {name}!")
st.markdown("---")
st.markdown("""
<div style='text-align: center; padding: 1rem 0 2rem 0;'>
<h1 style='color: #1f2937;'>Admin Panel</h1>
<p style='color: #6b7280;'>Cloud data sync controls</p>
</div>
""", unsafe_allow_html=True)
st.markdown("---")
# Tabs
tab1, tab2, tab3 = st.tabs(["Dashboard", "Data Pipeline", "User Management"])
# ------------------------------------------------------------------
# TAB 1 — Dashboard
# ------------------------------------------------------------------
with tab1:
st.subheader("Admin Dashboard")
users = config['credentials']['usernames']
admin_data = [
{
"Username": uname,
"Name": data.get("name"),
"Email": data.get("email"),
"Current User": "Admin" if uname == username else ""
}
for uname, data in users.items()
]
st.dataframe(pd.DataFrame(admin_data), width="stretch", hide_index=True)
# ------------------------------------------------------------------
# TAB 2 — DATA PIPELINE
# ------------------------------------------------------------------
with tab2:
st.subheader("Data Pipeline")
if 'huggingface' not in st.secrets:
st.warning("Add HuggingFace credentials to `.streamlit/secrets.toml`")
st.stop()
from huggingface_upload import upload_to_huggingface, test_hf_connection
# --- Connection Test
st.markdown("Connection Status")
col1, col2 = st.columns(2)
with col1:
if st.button("Test HuggingFace Connection", width='stretch'):
ok, msg = test_hf_connection()
(st.success if ok else st.error)(msg)
with col2:
repo = st.secrets["huggingface"]["dataset_repo"]
st.info(f"Dataset: {repo}")
st.markdown("---")
# --- Full Data Update Section
st.subheader("Full Data Update")
st.info("Pull new data, process PDFs, generate embeddings, and upload to HuggingFace.")
# ➤ NEW UI CONTROL — Pull new data?
pull_new_data = st.radio(
"Pull new data from LegiScan?",
options=[
("no", "No - Use existing local data"),
("yes", "Yes - Pull fresh data (costs API quota)"),
],
format_func=lambda x: x[1],
index=0,
key="pull_option"
)
# ➤ NEW UI CONTROL — overwrite known_bills.json?
overwrite_pdf = st.radio(
"After fixing PDF bills, overwrite data/known_bills.json?",
options=[
("no", "No - keep original file"),
("yes", "Yes - overwrite with cleaned PDF text"),
],
format_func=lambda x: x[1],
index=0,
key="overwrite_option"
)
# Run full update
if st.button("Run Full Update & Upload", type="primary", width='stretch'):
status_container = st.container()
with status_container:
st.markdown("### Step 1: Running Data Pipeline")
with st.status("Processing data...", expanded=True) as status:
try:
update_cmd = [sys.executable, "update_data.py"]
legiscan_answer = "y\n" if pull_new_data[0] == "yes" else "n\n"
import os
from dotenv import load_dotenv
load_dotenv()
env = os.environ.copy()
# Pass OpenAI keys (existing logic)
openai_key = (
st.secrets.get("openai_api_key")
or st.secrets.get("OPENAI_API_KEY")
or env.get("openai_api_key")
or env.get("OPENAI_API_KEY")
)
if openai_key:
env["OPENAI_API_KEY"] = openai_key
env["openai_api_key"] = openai_key
st.success("OpenAI key found")
else:
st.warning("OpenAI API key missing!")
# ➤ NEW: Pass PDF overwrite decision into environment
env["FIX_PDF_OVERWRITE"] = (
"yes" if overwrite_pdf[0] == "yes" else "no"
)
log_file = Path("pipeline_last_run.log")
with log_file.open("w", encoding="utf-8") as lf:
proc = subprocess.Popen(
update_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE,
text=True,
bufsize=1,
env=env,
)
# Send LegiScan yes/no
try:
proc.stdin.write(legiscan_answer)
proc.stdin.write("n\n") # continue-on-error prompt
proc.stdin.flush()
proc.stdin.close()
except:
pass
# Stream output
for line in proc.stdout:
line = line.rstrip("\n")
st.text(line)
lf.write(line + "\n")
rc = proc.wait()
if rc == 0:
status.update(label="Data pipeline completed", state="complete")
st.success("Processing successful!")
st.markdown("---")
st.markdown("### Step 2: Uploading to HuggingFace")
with st.spinner("Uploading..."):
url = upload_to_huggingface()
st.success("Uploaded to HuggingFace!")
st.code(url)
st.cache_data.clear()
else:
status.update(label="Pipeline failed", state="error")
st.error(f"Pipeline exited with code {rc}")
except Exception as e:
st.error(f"Pipeline error: {e}")
st.exception(e)
st.markdown("---")
with st.expander("Manual Upload Only"):
st.info("Use this only when skipping update_data.py")
if st.button("Upload Existing Data", width='stretch'):
with st.spinner("Uploading..."):
url = upload_to_huggingface()
st.success("Uploaded!")
st.code(url)
with tab3:
st.subheader("User Management")
if using_hf:
st.success("Using HuggingFace for persistent user storage")
try:
user_manager = HuggingFaceUserManager()
st.markdown("Add New Admin")
with st.form("add_user_form"):
col1, col2 = st.columns(2)
with col1:
new_username = st.text_input("Username", key="new_username")
new_email = st.text_input("Email", key="new_email")
with col2:
new_name = st.text_input("Full Name", key="new_name")
new_password = st.text_input("Password", type="password", key="new_password")
submit_add = st.form_submit_button("Add Admin", type="primary", width='stretch')
if submit_add:
if not all([new_username, new_email, new_name, new_password]):
st.error("Please fill in all fields")
else:
with st.spinner("Adding user..."):
import bcrypt
hashed_password = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode()
success, message, commit_url = user_manager.add_user(
new_username, new_email, new_name, hashed_password
)
if success:
st.success(f"{message}")
st.cache_data.clear()
if commit_url:
with st.expander("View commit"):
st.code(commit_url)
st.rerun()
else:
st.error(f"{message}")
st.markdown("---")
st.markdown("Edit Admin")
users = config['credentials']['usernames']
usernames_list = list(users.keys())
with st.form("edit_user_form"):
user_to_edit = st.selectbox(
"Select user to edit",
options=usernames_list,
key="edit_username"
)
current_user = users.get(user_to_edit, {})
st.markdown("**Current Details:**")
st.text(f"Email: {current_user.get('email', 'N/A')}")
st.text(f"Name: {current_user.get('name', 'N/A')}")
st.markdown("**New Details** (leave blank to keep current):")
col1, col2 = st.columns(2)
with col1:
new_email = st.text_input("New Email", key="edit_email", placeholder="Leave blank to keep current")
new_password = st.text_input("New Password", type="password", key="edit_password", placeholder="Leave blank to keep current")
with col2:
new_name = st.text_input("New Name", key="edit_name", placeholder="Leave blank to keep current")
submit_edit = st.form_submit_button("Update Admin", type="primary", width='stretch')
if submit_edit:
if not any([new_email, new_name, new_password]):
st.warning("Please enter at least one field to update")
else:
with st.spinner("Updating user..."):
hashed_password = None
if new_password:
import bcrypt
hashed_password = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode()
success, message, commit_url = user_manager.update_user(
user_to_edit,
new_email=new_email if new_email else None,
new_name=new_name if new_name else None,
new_password=hashed_password
)
if success:
st.success(f"{message}")
st.info("Refreshing user data...")
st.cache_data.clear()
if commit_url:
with st.expander("View commit"):
st.code(commit_url)
st.info("Please log out and log back in if you changed your own password")
st.rerun()
else:
st.error(f"{message}")
st.markdown("---")
# Remove user
st.markdown("Remove Admin")
users = config['credentials']['usernames']
usernames_list = list(users.keys())
if len(usernames_list) > 1:
with st.form("remove_user_form"):
user_to_remove = st.selectbox(
"Select user to remove",
options=usernames_list,
key="remove_username"
)
st.warning(f"This will permanently delete user: **{user_to_remove}**")
confirm_remove = st.checkbox("I confirm I want to remove this user")
submit_remove = st.form_submit_button("Remove Admin", type="secondary", width='stretch')
if submit_remove:
if not confirm_remove:
st.error("Please confirm the removal")
elif user_to_remove == username:
st.error("You cannot remove yourself!")
else:
with st.spinner("Removing user..."):
success, message, commit_url = user_manager.remove_user(user_to_remove)
if success:
st.success(f"✅ {message}")
st.cache_data.clear()
if commit_url:
with st.expander("View commit"):
st.code(commit_url)
st.rerun()
else:
st.error(f"{message}")
else:
st.info("ℹCannot remove the last admin user")
st.markdown("---")
# Show current users
st.markdown("Current Admins")
for uname, udata in users.items():
with st.expander(f"{udata.get('name', uname)} (@{uname})"):
st.write(f"**Email:** {udata.get('email', 'N/A')}")
st.write(f"**Username:** {uname}")
st.write(f"**Admin Status:**Admin")
if uname == username:
st.info("This is you!")
except Exception as e:
st.error(f"Error initializing user manager: {e}")
st.exception(e)
else:
st.warning("Using secrets.toml (read-only)")
st.info("For persistent user management, add HuggingFace credentials to secrets.toml")
with st.expander("How to add users manually"):
st.markdown("""
**To add new users when using secrets.toml:**
1. **Generate password hash:**
```bash
python generate_password_hash.py
```
2. **Add to secrets.toml:**
```toml
[auth.credentials.usernames.newuser]
email = "user@vanderbilt.edu"
name = "New User"
password = "$2b$12$HASH_FROM_STEP_1"
```
3. **Update on HuggingFace Spaces** (re-upload secrets.toml)
All registered users automatically get admin access.
""")
st.markdown("---")
st.markdown("Current Admins")
if 'credentials' in config and 'usernames' in config['credentials']:
users = config['credentials']['usernames']
for uname, udata in users.items():
with st.expander(f"{udata.get('name', uname)} (@{uname})"):
st.write(f"**Email:** {udata.get('email', 'N/A')}")
st.write(f"**Username:** {uname}")
st.write(f"**Admin Status:Admin")