|
|
import streamlit as st |
|
|
import boto3 |
|
|
import time |
|
|
from datetime import datetime |
|
|
import os |
|
|
|
|
|
|
|
|
DYNAMODB_REGION = os.getenv("DYNAMODB_REGION", "ap-south-1") |
|
|
SESSION_TABLE = os.getenv("SESSION_TABLE", "SessionTracking") |
|
|
COURSE_TABLE = os.getenv("COURSE_TABLE", "CourseTracking") |
|
|
|
|
|
|
|
|
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID") |
|
|
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY") |
|
|
|
|
|
def fetch_courses(): |
|
|
"""Fetch all courses from DynamoDB""" |
|
|
try: |
|
|
dynamodb = boto3.client( |
|
|
"dynamodb", |
|
|
region_name=DYNAMODB_REGION, |
|
|
aws_access_key_id=AWS_ACCESS_KEY_ID, |
|
|
aws_secret_access_key=AWS_SECRET_ACCESS_KEY, |
|
|
) |
|
|
|
|
|
response = dynamodb.scan(TableName=COURSE_TABLE) |
|
|
items = response.get("Items", []) |
|
|
courses = [] |
|
|
for item in items: |
|
|
course_detail = item.get("course_detail", {}).get("M", {}) |
|
|
courses.append({ |
|
|
"course_id": item.get("course_id", {}).get("N"), |
|
|
"course_name": item.get("course_name", {}).get("S"), |
|
|
"total_videos": item.get("total_videos", {}).get("N", "0"), |
|
|
"created_at": item.get("created_at", {}).get("S", "-"), |
|
|
"topic_id": course_detail.get("topic_id", {}).get("N", "-"), |
|
|
"chapter_id": course_detail.get("chapter_id", {}).get("N", "-"), |
|
|
}) |
|
|
return courses |
|
|
except Exception as e: |
|
|
st.error(f"β οΈ Error fetching courses: {e}") |
|
|
return [] |
|
|
|
|
|
def fetch_sessions(): |
|
|
"""Fetch all sessions from DynamoDB""" |
|
|
try: |
|
|
dynamodb = boto3.client( |
|
|
"dynamodb", |
|
|
region_name=DYNAMODB_REGION, |
|
|
aws_access_key_id=AWS_ACCESS_KEY_ID, |
|
|
aws_secret_access_key=AWS_SECRET_ACCESS_KEY, |
|
|
) |
|
|
|
|
|
response = dynamodb.scan(TableName=SESSION_TABLE) |
|
|
items = response.get("Items", []) |
|
|
sessions = [] |
|
|
for item in items: |
|
|
sessions.append({ |
|
|
"session_id": item.get("session_id", {}).get("S"), |
|
|
"course_id": item.get("course_id", {}).get("N"), |
|
|
"topic_id": item.get("topic_id", {}).get("N"), |
|
|
"topic_title": item.get("topic_title", {}).get("S", "-"), |
|
|
"status": item.get("status", {}).get("S", "unknown"), |
|
|
"node": item.get("node", {}).get("S", "-"), |
|
|
"created_at": item.get("created_at", {}).get("S", "-"), |
|
|
"updated_at": item.get("updated_at", {}).get("S", "-"), |
|
|
"video_url": item.get("video_url", {}).get("S", None), |
|
|
}) |
|
|
return sessions |
|
|
except Exception as e: |
|
|
st.error(f"β οΈ Error fetching sessions: {e}") |
|
|
return [] |
|
|
|
|
|
def get_sessions_by_course(course_id): |
|
|
"""Get sessions for a specific course""" |
|
|
all_sessions = fetch_sessions() |
|
|
return [session for session in all_sessions if session['course_id'] == str(course_id)] |
|
|
|
|
|
def render_admin_dashboard(): |
|
|
"""Render the admin dashboard page""" |
|
|
st.markdown("<h1 style='color:#4F97FF;text-align:center;'>π Admin Dashboard</h1>", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 1, 2]) |
|
|
with col1: |
|
|
if st.button("π Refresh Now", key="manual_refresh"): |
|
|
st.rerun() |
|
|
|
|
|
with col2: |
|
|
auto_refresh = st.toggle("Auto Refresh (30s)", value=False) |
|
|
|
|
|
|
|
|
tab1, tab2, tab3 = st.tabs(["π Courses Overview", "π¬ Sessions Overview", "π Course Details"]) |
|
|
|
|
|
with tab1: |
|
|
render_courses_overview() |
|
|
|
|
|
with tab2: |
|
|
render_sessions_overview() |
|
|
|
|
|
with tab3: |
|
|
render_course_details() |
|
|
|
|
|
|
|
|
if auto_refresh: |
|
|
time.sleep(30) |
|
|
st.rerun() |
|
|
|
|
|
def render_courses_overview(): |
|
|
"""Render courses overview tab""" |
|
|
st.markdown("### π All Courses") |
|
|
|
|
|
courses = fetch_courses() |
|
|
|
|
|
if not courses: |
|
|
st.warning("No courses found yet.") |
|
|
st.info("Courses will appear here once you create them from the Course Generation page.") |
|
|
return |
|
|
|
|
|
|
|
|
st.markdown(f"**Total Courses: {len(courses)}**") |
|
|
|
|
|
|
|
|
try: |
|
|
courses.sort(key=lambda x: x['created_at'] if x['created_at'] != '-' else '1970-01-01', reverse=True) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
for i, course in enumerate(courses): |
|
|
with st.container(): |
|
|
st.markdown(f""" |
|
|
<div style="background:#2D2D2D;padding:1.5rem;border-radius:12px;margin-bottom:1rem;border:1px solid #444;"> |
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;"> |
|
|
<h4 style="color:#4F97FF;margin:0;">π {course['course_name']}</h4> |
|
|
<span style="background:#10B981;color:black;padding:0.3rem 0.8rem;border-radius:15px;font-size:0.8rem;font-weight:bold;">COURSE ID: {course['course_id']}</span> |
|
|
</div> |
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;"> |
|
|
<div> |
|
|
<b>πΉ Total Videos:</b><br><span style="color:#E0E0E0;">{course['total_videos']}</span> |
|
|
</div> |
|
|
<div> |
|
|
<b>π― Topic ID:</b><br><span style="color:#E0E0E0;">{course['topic_id']}</span> |
|
|
</div> |
|
|
<div> |
|
|
<b>π Chapter ID:</b><br><span style="color:#E0E0E0;">{course['chapter_id']}</span> |
|
|
</div> |
|
|
</div> |
|
|
<div style="margin-top:1rem;"> |
|
|
<b>π
Created:</b> <span style="color:#888;">{course['created_at'][:19] if course['created_at'] != '-' else '-'}</span> |
|
|
</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
def render_sessions_overview(): |
|
|
"""Render sessions overview tab""" |
|
|
st.markdown("### π¬ All Sessions") |
|
|
|
|
|
sessions = fetch_sessions() |
|
|
|
|
|
if not sessions: |
|
|
st.warning("No sessions found yet.") |
|
|
st.info("Sessions will appear here once you start generating courses.") |
|
|
return |
|
|
|
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
status_counts = {} |
|
|
for session in sessions: |
|
|
status = session['status'].lower() |
|
|
status_counts[status] = status_counts.get(status, 0) + 1 |
|
|
|
|
|
with col1: |
|
|
st.metric("π Total Sessions", len(sessions)) |
|
|
with col2: |
|
|
completed = status_counts.get('completed', 0) |
|
|
st.metric("β
Completed", completed) |
|
|
with col3: |
|
|
processing = status_counts.get('processing', 0) + status_counts.get('in_progress', 0) + status_counts.get('running', 0) |
|
|
st.metric("β³ Processing", processing) |
|
|
with col4: |
|
|
failed = status_counts.get('failed', 0) + status_counts.get('error', 0) |
|
|
st.metric("β Failed", failed) |
|
|
|
|
|
|
|
|
try: |
|
|
sessions.sort(key=lambda x: x['updated_at'] if x['updated_at'] != '-' else '1970-01-01', reverse=True) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
for i, session in enumerate(sessions): |
|
|
status_color = { |
|
|
'completed': '#10B981', |
|
|
'processing': '#F59E0B', |
|
|
'in_progress': '#F59E0B', |
|
|
'running': '#F59E0B', |
|
|
'failed': '#EF4444', |
|
|
'error': '#EF4444' |
|
|
}.get(session['status'].lower(), '#6B7280') |
|
|
|
|
|
with st.container(): |
|
|
st.markdown(f""" |
|
|
<div style="background:#2D2D2D;padding:1.5rem;border-radius:12px;margin-bottom:1rem;border:1px solid #444;"> |
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;"> |
|
|
<h4 style="color:#4F97FF;margin:0;">π¬ {session['topic_title']}</h4> |
|
|
<span style="background:{status_color};color:white;padding:0.3rem 0.8rem;border-radius:15px;font-size:0.8rem;font-weight:bold;">{session['status'].upper()}</span> |
|
|
</div> |
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;"> |
|
|
<div> |
|
|
<b>π Session ID:</b><br><code style="background:#1A1A1A;padding:0.2rem;border-radius:4px;font-size:0.8rem;">{session['session_id'][:20]}...</code> |
|
|
</div> |
|
|
<div> |
|
|
<b>π Course ID:</b><br><span style="color:#E0E0E0;">{session['course_id']}</span> |
|
|
</div> |
|
|
<div> |
|
|
<b>π― Topic ID:</b><br><span style="color:#E0E0E0;">{session['topic_id']}</span> |
|
|
</div> |
|
|
</div> |
|
|
<div style="margin-top:1rem;display:flex;justify-content:space-between;"> |
|
|
<div><b>π§ Current Node:</b> <span style="color:#E0E0E0;">{session['node']}</span></div> |
|
|
<div><b>β±οΈ Updated:</b> <span style="color:#888;">{session['updated_at'][:19] if session['updated_at'] != '-' else '-'}</span></div> |
|
|
</div> |
|
|
{f"<div style='margin-top:1rem;'><b>π₯ Video:</b> <a href='{session['video_url']}' target='_blank' style='color:#4F97FF;'>πΊ Watch Video</a></div>" if session['video_url'] else ""} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
def render_course_details(): |
|
|
"""Render detailed course view with sessions""" |
|
|
st.markdown("### π Detailed Course View") |
|
|
|
|
|
courses = fetch_courses() |
|
|
|
|
|
if not courses: |
|
|
st.warning("No courses found.") |
|
|
return |
|
|
|
|
|
|
|
|
course_options = {f"{course['course_name']} (ID: {course['course_id']})": course['course_id'] for course in courses} |
|
|
|
|
|
if course_options: |
|
|
selected_course_display = st.selectbox( |
|
|
"Select a course to view details:", |
|
|
options=list(course_options.keys()), |
|
|
key="course_detail_selector" |
|
|
) |
|
|
|
|
|
selected_course_id = course_options[selected_course_display] |
|
|
selected_course = next(course for course in courses if course['course_id'] == str(selected_course_id)) |
|
|
|
|
|
|
|
|
st.markdown(f""" |
|
|
<div style="background:#2D2D2D;padding:2rem;border-radius:12px;margin-bottom:2rem;border:1px solid #444;"> |
|
|
<h3 style="color:#4F97FF;margin-bottom:1rem;">π {selected_course['course_name']}</h3> |
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;"> |
|
|
<div><b>π Course ID:</b> {selected_course['course_id']}</div> |
|
|
<div><b>πΉ Total Videos:</b> {selected_course['total_videos']}</div> |
|
|
<div><b>π
Created:</b> {selected_course['created_at'][:19] if selected_course['created_at'] != '-' else '-'}</div> |
|
|
</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
course_sessions = get_sessions_by_course(selected_course_id) |
|
|
|
|
|
if course_sessions: |
|
|
st.markdown(f"#### π¬ Sessions for this Course ({len(course_sessions)} sessions)") |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
course_status_counts = {} |
|
|
for session in course_sessions: |
|
|
status = session['status'].lower() |
|
|
course_status_counts[status] = course_status_counts.get(status, 0) + 1 |
|
|
|
|
|
with col1: |
|
|
completed = course_status_counts.get('completed', 0) |
|
|
st.metric("β
Completed", completed) |
|
|
with col2: |
|
|
processing = course_status_counts.get('processing', 0) + course_status_counts.get('in_progress', 0) + course_status_counts.get('running', 0) |
|
|
st.metric("β³ Processing", processing) |
|
|
with col3: |
|
|
failed = course_status_counts.get('failed', 0) + course_status_counts.get('error', 0) |
|
|
st.metric("β Failed", failed) |
|
|
|
|
|
|
|
|
for session in course_sessions: |
|
|
status_color = { |
|
|
'completed': '#10B981', |
|
|
'processing': '#F59E0B', |
|
|
'in_progress': '#F59E0B', |
|
|
'running': '#F59E0B', |
|
|
'failed': '#EF4444', |
|
|
'error': '#EF4444' |
|
|
}.get(session['status'].lower(), '#6B7280') |
|
|
|
|
|
st.markdown(f""" |
|
|
<div style="background:#1E1E1E;padding:1rem;border-radius:8px;margin-bottom:0.5rem;border-left:4px solid {status_color};"> |
|
|
<div style="display:flex;justify-content:between;align-items:center;"> |
|
|
<div style="flex:2;"><b>{session['topic_title']}</b></div> |
|
|
<div style="flex:1;text-align:center;"><span style="background:{status_color};color:white;padding:0.2rem 0.6rem;border-radius:10px;font-size:0.75rem;">{session['status'].upper()}</span></div> |
|
|
<div style="flex:1;text-align:right;color:#888;font-size:0.8rem;">{session['node']}</div> |
|
|
</div> |
|
|
{f"<div style='margin-top:0.5rem;'><a href='{session['video_url']}' target='_blank' style='color:#4F97FF;font-size:0.8rem;'>π₯ Watch Video</a></div>" if session['video_url'] else ""} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
else: |
|
|
st.info("No sessions found for this course yet.") |