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:
parent
8a33bd5b9a
commit
fbefffe002
4 changed files with 231 additions and 15 deletions
|
|
@ -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 🦙")
|
||||
|
|
|
|||
201
src/notebookllama/pages/5_Admin_Dashboard.py
Normal file
201
src/notebookllama/pages/5_Admin_Dashboard.py
Normal 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")
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue