Add logo to all pages, hide sidebar on login, create Admin Dashboard

Logo Implementation:
- Logo now appears on ALL pages automatically
- Moved logo display to apply_custom_styles() function
- 300px width, positioned at top with margin adjustments
- Uses SBLM.jpg from src/notebookllama/

Login Page Improvements:
- Sidebar completely hidden when not logged in
- Cleaner login experience
- No navigation shown to unauthenticated users
- Title updated to "Sandbox-NotebookLM - Login"

Admin Dashboard:
- Replaced Observability Dashboard with Admin Dashboard
- Shows platform usage statistics:
  * Total users, notebooks, documents, chat messages
  * Estimated costs (documents, chats, podcasts)
  * Recent activity (users, notebooks, documents)
  * Background task monitoring
  * User analytics (most active users)
  * System health (failed/pending tasks)

Admin Access Control:
- Only user ID 1 or users with "admin" in email can access
- Admin link only shows in sidebar for admins
- Protected with access check

Cost Tracking:
- Document processing: ~$0.60 each
- Chat messages: ~$0.01 each
- Podcasts: ~$0.50 each
- Total cost estimate displayed

Analytics Features:
- Top 5 most active users by notebook count
- Recent users/notebooks/documents lists
- Background task status with color coding
- System health indicators
- Real-time statistics

Files:
- Created pages/5_Admin_Dashboard.py
- Removed pages/5_Observability_Dashboard.py (backed up)
- Updated styles.py with show_logo() function
- Updated App.py navigation to show Admin conditionally

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DJP 2025-10-01 19:10:51 -04:00
parent 8a33bd5b9a
commit fbefffe002
4 changed files with 231 additions and 15 deletions

View file

@ -9,26 +9,21 @@ st.set_page_config(
initial_sidebar_state="expanded"
)
# Apply custom styles
# Apply custom styles (includes logo)
apply_custom_styles()
# Display logo at top
import base64
from pathlib import Path
logo_path = Path(__file__).parent / "SBLM.jpg"
if logo_path.exists():
with open(logo_path, "rb") as f:
logo_data = base64.b64encode(f.read()).decode()
st.markdown(
f'<div style="text-align: left; margin-bottom: 1rem;"><img src="data:image/jpeg;base64,{logo_data}" width="300"></div>',
unsafe_allow_html=True
)
# Check if user is logged in
user = get_current_user()
if not user:
# Hide sidebar when not logged in
st.markdown("""
<style>
[data-testid="stSidebar"] {
display: none !important;
}
</style>
""", unsafe_allow_html=True)
show_login_page()
else:
# User is logged in - show sidebar with navigation
@ -45,7 +40,10 @@ else:
st.page_link("App.py", label="🏠 Dashboard")
st.page_link("pages/1_My_Notebooks.py", label="📚 My Notebooks")
st.page_link("pages/4_Shared_Notebooks.py", label="🤝 Shared With Me")
st.page_link("pages/5_Observability_Dashboard.py", label="📊 Observability")
# Admin link (only show if user is admin)
if user.id == 1 or "admin" in user.email.lower():
st.page_link("pages/5_Admin_Dashboard.py", label="⚙️ Admin")
# Main dashboard content
st.title("Sandbox-NotebookLM Dashboard 🦙")

View file

@ -0,0 +1,201 @@
import streamlit as st
from datetime import datetime, timedelta
from auth import require_auth, get_current_user
from styles import apply_custom_styles
from database import get_db, User, Notebook, Document, DocumentSummary, ChatSession, ChatMessage, BackgroundTask
from sqlalchemy import func, desc
require_auth()
user = get_current_user()
st.set_page_config(page_title="Sandbox-NotebookLM - Admin", page_icon="⚙️", layout="wide")
apply_custom_styles()
st.title("⚙️ Admin Dashboard")
st.markdown("---")
# Check if user is admin (for now, just check if they're the first user or have admin email)
is_admin = user.id == 1 or "admin" in user.email.lower()
if not is_admin:
st.warning("⚠️ Admin access required")
st.info("This page is only accessible to administrators.")
st.stop()
# Get database session
db = get_db()
try:
# === USAGE STATISTICS ===
st.subheader("📊 Platform Usage")
col1, col2, col3, col4 = st.columns(4)
with col1:
total_users = db.query(User).count()
st.metric("Total Users", total_users)
with col2:
total_notebooks = db.query(Notebook).count()
st.metric("Total Notebooks", total_notebooks)
with col3:
total_documents = db.query(Document).count()
st.metric("Total Documents", total_documents)
with col4:
total_chats = db.query(ChatMessage).count()
st.metric("Total Chat Messages", total_chats)
st.markdown("---")
# === COST ESTIMATION ===
st.subheader("💰 Estimated Costs")
total_summaries = db.query(DocumentSummary).count()
podcasts_generated = db.query(Notebook).filter(Notebook.podcast_path.isnot(None)).count()
col_c1, col_c2, col_c3, col_c4 = st.columns(4)
with col_c1:
doc_cost = total_summaries * 0.60
st.metric("Document Processing", f"${doc_cost:.2f}", help="~$0.60 per document")
with col_c2:
chat_cost = total_chats * 0.01
st.metric("Chat Messages", f"${chat_cost:.2f}", help="~$0.01 per message")
with col_c3:
podcast_cost = podcasts_generated * 0.50
st.metric("Podcasts Generated", f"${podcast_cost:.2f}", help="~$0.50 per podcast")
with col_c4:
total_cost = doc_cost + chat_cost + podcast_cost
st.metric("Total Estimated", f"${total_cost:.2f}", delta=None)
st.caption("💡 These are estimates based on average API usage. Actual costs may vary.")
st.markdown("---")
# === RECENT ACTIVITY ===
st.subheader("🕐 Recent Activity")
tab1, tab2, tab3 = st.tabs(["Users", "Notebooks", "Documents"])
with tab1:
st.markdown("**Recent Users:**")
recent_users = db.query(User).order_by(desc(User.created_at)).limit(10).all()
for u in recent_users:
col_u1, col_u2, col_u3 = st.columns([2, 2, 1])
with col_u1:
st.write(f"👤 {u.username}")
with col_u2:
st.caption(u.email)
with col_u3:
st.caption(u.created_at.strftime('%Y-%m-%d'))
with tab2:
st.markdown("**Recent Notebooks:**")
recent_notebooks = db.query(Notebook).order_by(desc(Notebook.created_at)).limit(10).all()
for nb in recent_notebooks:
col_n1, col_n2, col_n3 = st.columns([3, 2, 1])
with col_n1:
st.write(f"📓 {nb.name}")
with col_n2:
st.caption(f"by {nb.owner.username}")
with col_n3:
st.caption(nb.created_at.strftime('%Y-%m-%d'))
with tab3:
st.markdown("**Recent Documents:**")
recent_docs = db.query(Document).order_by(desc(Document.created_at)).limit(10).all()
for doc in recent_docs:
col_d1, col_d2, col_d3 = st.columns([3, 2, 1])
with col_d1:
st.write(f"📄 {doc.original_filename}")
with col_d2:
st.caption(f"by {doc.owner.username}")
with col_d3:
st.caption(doc.created_at.strftime('%Y-%m-%d'))
st.markdown("---")
# === BACKGROUND TASKS ===
st.subheader("⚙️ Background Tasks")
tasks = db.query(BackgroundTask).order_by(desc(BackgroundTask.created_at)).limit(20).all()
if tasks:
for task in tasks:
status_color = {
'pending': '🟡',
'in_progress': '🔵',
'completed': '',
'failed': ''
}.get(task.status.value, '')
col_t1, col_t2, col_t3, col_t4 = st.columns([2, 2, 1, 1])
with col_t1:
st.write(f"{status_color} {task.task_type}")
with col_t2:
if task.notebook_id:
notebook = db.query(Notebook).filter(Notebook.id == task.notebook_id).first()
if notebook:
st.caption(f"Notebook: {notebook.name}")
with col_t3:
st.caption(task.status.value)
with col_t4:
st.caption(task.created_at.strftime('%H:%M'))
else:
st.info("No background tasks yet")
st.markdown("---")
# === USER ANALYTICS ===
st.subheader("👥 User Analytics")
# Top users by notebooks
top_users = db.query(
User.username,
func.count(Notebook.id).label('notebook_count')
).join(Notebook).group_by(User.id, User.username).order_by(desc('notebook_count')).limit(5).all()
if top_users:
st.markdown("**Most Active Users (by notebooks):**")
for username, count in top_users:
st.write(f"👤 {username}: {count} notebook(s)")
st.markdown("---")
# === SYSTEM HEALTH ===
st.subheader("🏥 System Health")
col_h1, col_h2, col_h3 = st.columns(3)
with col_h1:
# Check failed tasks
failed_tasks = db.query(BackgroundTask).filter(BackgroundTask.status == 'failed').count()
if failed_tasks > 0:
st.error(f"⚠️ {failed_tasks} failed tasks")
else:
st.success("✅ No failed tasks")
with col_h2:
# Check pending tasks
pending_tasks = db.query(BackgroundTask).filter(
BackgroundTask.status.in_(['pending', 'in_progress'])
).count()
if pending_tasks > 0:
st.info(f"🔄 {pending_tasks} tasks in progress")
else:
st.success("✅ No pending tasks")
with col_h3:
# Database size estimate
st.info(f"📊 {total_documents} docs + {podcasts_generated} podcasts")
finally:
db.close()
st.markdown("---")
st.caption("Admin Dashboard - Real-time platform statistics and monitoring")

View file

@ -170,7 +170,24 @@ def get_custom_css():
"""
def show_logo():
"""Display the logo at the top of the page"""
import streamlit as st
import base64
from pathlib import Path
logo_path = Path(__file__).parent / "SBLM.jpg"
if logo_path.exists():
with open(logo_path, "rb") as f:
logo_data = base64.b64encode(f.read()).decode()
st.markdown(
f'<div style="text-align: left; margin-bottom: 1.5rem; margin-top: -1rem;"><img src="data:image/jpeg;base64,{logo_data}" width="300"></div>',
unsafe_allow_html=True
)
def apply_custom_styles():
"""Apply custom CSS to the current page"""
import streamlit as st
st.markdown(get_custom_css(), unsafe_allow_html=True)
show_logo()