diff --git a/src/notebookllama/App.py b/src/notebookllama/App.py
index 3562029..f14c56f 100644
--- a/src/notebookllama/App.py
+++ b/src/notebookllama/App.py
@@ -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'
',
- 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("""
+
+ """, 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 🦙")
diff --git a/src/notebookllama/pages/5_Admin_Dashboard.py b/src/notebookllama/pages/5_Admin_Dashboard.py
new file mode 100644
index 0000000..5254bdf
--- /dev/null
+++ b/src/notebookllama/pages/5_Admin_Dashboard.py
@@ -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")
diff --git a/src/notebookllama/pages/5_Observability_Dashboard.py b/src/notebookllama/pages/OLD_5_Observability_Dashboard.py.bak
similarity index 100%
rename from src/notebookllama/pages/5_Observability_Dashboard.py
rename to src/notebookllama/pages/OLD_5_Observability_Dashboard.py.bak
diff --git a/src/notebookllama/styles.py b/src/notebookllama/styles.py
index 2f112e2..fa3befe 100644
--- a/src/notebookllama/styles.py
+++ b/src/notebookllama/styles.py
@@ -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'',
+ 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()