386 lines
14 KiB
Python
386 lines
14 KiB
Python
"""
|
||
Main dashboard implementation for the Content Scheduler.
|
||
"""
|
||
|
||
import streamlit as st
|
||
import pandas as pd
|
||
from datetime import datetime, timedelta
|
||
from typing import List, Dict, Any
|
||
import plotly.express as px
|
||
import plotly.graph_objects as go
|
||
from lib.database.models import ContentItem, Schedule, ScheduleStatus, ContentType, Platform, get_engine, get_session, init_db
|
||
|
||
engine = get_engine()
|
||
init_db(engine)
|
||
session = get_session(engine)
|
||
|
||
def run_dashboard():
|
||
"""Run the Streamlit dashboard."""
|
||
|
||
st.title("📅 Alwrity Content Scheduler Dashboard")
|
||
|
||
# Sidebar navigation
|
||
st.sidebar.title("Navigation")
|
||
page = st.sidebar.radio(
|
||
"Go to",
|
||
["Overview", "Schedule Management", "Create Schedule", "Job Monitor", "Analytics"]
|
||
)
|
||
|
||
if page == "Overview":
|
||
show_overview()
|
||
elif page == "Schedule Management":
|
||
show_schedule_management()
|
||
elif page == "Create Schedule":
|
||
show_create_schedule()
|
||
elif page == "Job Monitor":
|
||
show_job_monitor()
|
||
else:
|
||
show_analytics()
|
||
|
||
def show_overview():
|
||
"""Display the overview dashboard."""
|
||
st.header("📊 Overview")
|
||
|
||
# Get data from unified database
|
||
all_content = session.query(ContentItem).all()
|
||
all_schedules = session.query(Schedule).all()
|
||
|
||
# Display metrics
|
||
col1, col2, col3, col4 = st.columns(4)
|
||
|
||
with col1:
|
||
st.metric("Total Content Items", len(all_content))
|
||
|
||
with col2:
|
||
scheduled_count = len([s for s in all_schedules if s.status == ScheduleStatus.SCHEDULED])
|
||
st.metric("Scheduled Items", scheduled_count)
|
||
|
||
with col3:
|
||
completed_count = len([s for s in all_schedules if s.status == ScheduleStatus.COMPLETED])
|
||
st.metric("Completed", completed_count)
|
||
|
||
with col4:
|
||
failed_count = len([s for s in all_schedules if s.status == ScheduleStatus.FAILED])
|
||
st.metric("Failed", failed_count)
|
||
|
||
# Recent content
|
||
st.subheader("📝 Recent Content Items")
|
||
if all_content:
|
||
recent_content = sorted(all_content, key=lambda x: x.created_at, reverse=True)[:5]
|
||
for item in recent_content:
|
||
with st.expander(f"{item.title} ({item.content_type.value})"):
|
||
st.write(f"**Description:** {item.description or 'No description'}")
|
||
st.write(f"**Platforms:** {', '.join(item.platforms) if isinstance(item.platforms, list) else item.platforms}")
|
||
st.write(f"**Status:** {item.status}")
|
||
st.write(f"**Created:** {item.created_at}")
|
||
|
||
# Show associated schedules
|
||
item_schedules = [s for s in all_schedules if s.content_item_id == item.id]
|
||
if item_schedules:
|
||
st.write("**Schedules:**")
|
||
for schedule in item_schedules:
|
||
st.write(f" - {schedule.scheduled_time} ({schedule.status.value})")
|
||
else:
|
||
st.info("No content items found. Create some content in the Content Calendar first!")
|
||
|
||
def show_schedule_management():
|
||
"""Display the schedule management interface."""
|
||
st.header("📅 Schedule Management")
|
||
|
||
# Get all schedules
|
||
all_schedules = session.query(Schedule).all()
|
||
|
||
if not all_schedules:
|
||
st.info("No schedules found. Create schedules from the 'Create Schedule' tab.")
|
||
return
|
||
|
||
# Filter options
|
||
col1, col2 = st.columns(2)
|
||
with col1:
|
||
status_filter = st.selectbox(
|
||
"Filter by Status",
|
||
options=["All"] + [status.value for status in ScheduleStatus],
|
||
key="schedule_status_filter"
|
||
)
|
||
|
||
with col2:
|
||
date_filter = st.date_input(
|
||
"Filter by Date (from)",
|
||
value=datetime.now().date() - timedelta(days=30),
|
||
key="schedule_date_filter"
|
||
)
|
||
|
||
# Apply filters
|
||
filtered_schedules = all_schedules
|
||
if status_filter != "All":
|
||
filtered_schedules = [s for s in filtered_schedules if s.status.value == status_filter]
|
||
|
||
filtered_schedules = [s for s in filtered_schedules if s.scheduled_time.date() >= date_filter]
|
||
|
||
# Display schedules
|
||
st.subheader(f"📋 Schedules ({len(filtered_schedules)} items)")
|
||
|
||
for schedule in sorted(filtered_schedules, key=lambda x: x.scheduled_time, reverse=True):
|
||
content_item = session.query(ContentItem).get(schedule.content_item_id)
|
||
|
||
if content_item:
|
||
with st.expander(f"{content_item.title} - {schedule.scheduled_time.strftime('%Y-%m-%d %H:%M')} ({schedule.status.value})"):
|
||
col1, col2 = st.columns(2)
|
||
|
||
with col1:
|
||
st.write(f"**Content:** {content_item.title}")
|
||
st.write(f"**Type:** {content_item.content_type.value}")
|
||
st.write(f"**Platforms:** {', '.join(content_item.platforms) if isinstance(content_item.platforms, list) else content_item.platforms}")
|
||
st.write(f"**Scheduled Time:** {schedule.scheduled_time}")
|
||
st.write(f"**Status:** {schedule.status.value}")
|
||
|
||
with col2:
|
||
st.write(f"**Recurrence:** {schedule.recurrence or 'One-time'}")
|
||
st.write(f"**Priority:** {schedule.priority}")
|
||
st.write(f"**Created:** {schedule.created_at}")
|
||
if schedule.result:
|
||
st.write(f"**Result:** {schedule.result}")
|
||
|
||
# Action buttons
|
||
col1, col2, col3 = st.columns(3)
|
||
|
||
with col1:
|
||
if st.button(f"Edit Schedule", key=f"edit_{schedule.id}"):
|
||
st.session_state.edit_schedule_id = schedule.id
|
||
st.rerun()
|
||
|
||
with col2:
|
||
if schedule.status == ScheduleStatus.SCHEDULED:
|
||
if st.button(f"Cancel", key=f"cancel_{schedule.id}"):
|
||
schedule.status = ScheduleStatus.CANCELLED
|
||
session.commit()
|
||
st.success("Schedule cancelled!")
|
||
st.rerun()
|
||
|
||
with col3:
|
||
if st.button(f"Delete", key=f"delete_{schedule.id}"):
|
||
session.delete(schedule)
|
||
session.commit()
|
||
st.success("Schedule deleted!")
|
||
st.rerun()
|
||
|
||
def show_create_schedule():
|
||
"""Display the schedule creation interface."""
|
||
st.header("➕ Create New Schedule")
|
||
|
||
# Get available content items
|
||
content_items = session.query(ContentItem).all()
|
||
|
||
if not content_items:
|
||
st.warning("No content items available. Please create content in the Content Calendar first.")
|
||
return
|
||
|
||
# Create schedule form
|
||
with st.form("create_schedule_form"):
|
||
st.subheader("Schedule Configuration")
|
||
|
||
# Select content item
|
||
content_options = {f"{item.title} ({item.content_type.value})": item.id for item in content_items}
|
||
selected_content = st.selectbox(
|
||
"Select Content Item",
|
||
options=list(content_options.keys()),
|
||
key="schedule_content_select"
|
||
)
|
||
|
||
# Schedule timing
|
||
col1, col2 = st.columns(2)
|
||
with col1:
|
||
schedule_date = st.date_input(
|
||
"Schedule Date",
|
||
value=datetime.now().date() + timedelta(days=1),
|
||
key="schedule_date"
|
||
)
|
||
|
||
with col2:
|
||
schedule_time = st.time_input(
|
||
"Schedule Time",
|
||
value=datetime.now().time(),
|
||
key="schedule_time"
|
||
)
|
||
|
||
# Combine date and time
|
||
schedule_datetime = datetime.combine(schedule_date, schedule_time)
|
||
|
||
# Recurrence options
|
||
recurrence = st.selectbox(
|
||
"Recurrence",
|
||
options=["none", "daily", "weekly", "monthly"],
|
||
key="schedule_recurrence"
|
||
)
|
||
|
||
# Priority
|
||
priority = st.slider(
|
||
"Priority",
|
||
min_value=1,
|
||
max_value=10,
|
||
value=5,
|
||
key="schedule_priority"
|
||
)
|
||
|
||
# Platform selection (override content item platforms if needed)
|
||
content_item_id = content_options[selected_content]
|
||
content_item = session.query(ContentItem).get(content_item_id)
|
||
|
||
if content_item:
|
||
current_platforms = content_item.platforms if isinstance(content_item.platforms, list) else [content_item.platforms]
|
||
st.write(f"**Current Platforms:** {', '.join(current_platforms)}")
|
||
|
||
override_platforms = st.checkbox("Override Platforms", key="override_platforms")
|
||
|
||
if override_platforms:
|
||
available_platforms = [p.value for p in Platform]
|
||
selected_platforms = st.multiselect(
|
||
"Select Platforms",
|
||
options=available_platforms,
|
||
default=current_platforms,
|
||
key="schedule_platforms"
|
||
)
|
||
else:
|
||
selected_platforms = current_platforms
|
||
|
||
# Submit button
|
||
submitted = st.form_submit_button("Create Schedule")
|
||
|
||
if submitted:
|
||
try:
|
||
# Create new schedule
|
||
new_schedule = Schedule(
|
||
content_item_id=content_item_id,
|
||
scheduled_time=schedule_datetime,
|
||
status=ScheduleStatus.SCHEDULED,
|
||
recurrence=recurrence if recurrence != "none" else None,
|
||
priority=priority
|
||
)
|
||
|
||
session.add(new_schedule)
|
||
session.commit()
|
||
|
||
st.success(f"✅ Schedule created successfully! Content will be published on {schedule_datetime}")
|
||
|
||
# Show schedule details
|
||
with st.expander("Schedule Details", expanded=True):
|
||
st.write(f"**Content:** {content_item.title}")
|
||
st.write(f"**Scheduled Time:** {schedule_datetime}")
|
||
st.write(f"**Platforms:** {', '.join(selected_platforms)}")
|
||
st.write(f"**Recurrence:** {recurrence}")
|
||
st.write(f"**Priority:** {priority}")
|
||
|
||
except Exception as e:
|
||
st.error(f"❌ Error creating schedule: {str(e)}")
|
||
|
||
def show_job_monitor():
|
||
"""Display the job monitoring interface."""
|
||
st.header("🔍 Job Monitor")
|
||
|
||
# Get all schedules with their status
|
||
all_schedules = session.query(Schedule).all()
|
||
|
||
if not all_schedules:
|
||
st.info("No jobs to monitor.")
|
||
return
|
||
|
||
# Status distribution
|
||
status_counts = {}
|
||
for schedule in all_schedules:
|
||
status = schedule.status.value
|
||
status_counts[status] = status_counts.get(status, 0) + 1
|
||
|
||
# Display status chart
|
||
if status_counts:
|
||
fig = px.pie(
|
||
values=list(status_counts.values()),
|
||
names=list(status_counts.keys()),
|
||
title="Job Status Distribution"
|
||
)
|
||
st.plotly_chart(fig, use_container_width=True)
|
||
|
||
# Recent job activity
|
||
st.subheader("📊 Recent Job Activity")
|
||
|
||
recent_schedules = sorted(all_schedules, key=lambda x: x.updated_at, reverse=True)[:10]
|
||
|
||
for schedule in recent_schedules:
|
||
content_item = session.query(ContentItem).get(schedule.content_item_id)
|
||
|
||
if content_item:
|
||
status_color = {
|
||
ScheduleStatus.SCHEDULED: "🟡",
|
||
ScheduleStatus.RUNNING: "🔵",
|
||
ScheduleStatus.COMPLETED: "🟢",
|
||
ScheduleStatus.FAILED: "🔴",
|
||
ScheduleStatus.CANCELLED: "⚫"
|
||
}.get(schedule.status, "⚪")
|
||
|
||
st.write(f"{status_color} **{content_item.title}** - {schedule.status.value} - {schedule.updated_at.strftime('%Y-%m-%d %H:%M')}")
|
||
|
||
if schedule.result:
|
||
st.write(f" └─ {schedule.result}")
|
||
|
||
def show_analytics():
|
||
"""Display the analytics dashboard."""
|
||
st.header("📈 Analytics")
|
||
|
||
# Get data
|
||
all_content = session.query(ContentItem).all()
|
||
all_schedules = session.query(Schedule).all()
|
||
|
||
if not all_schedules:
|
||
st.info("No data available for analytics.")
|
||
return
|
||
|
||
# Time-based analytics
|
||
st.subheader("📅 Schedule Timeline")
|
||
|
||
# Create timeline data
|
||
timeline_data = []
|
||
for schedule in all_schedules:
|
||
content_item = session.query(ContentItem).get(schedule.content_item_id)
|
||
if content_item:
|
||
timeline_data.append({
|
||
'Date': schedule.scheduled_time.date(),
|
||
'Content': content_item.title,
|
||
'Status': schedule.status.value,
|
||
'Type': content_item.content_type.value
|
||
})
|
||
|
||
if timeline_data:
|
||
df = pd.DataFrame(timeline_data)
|
||
|
||
# Schedule frequency by date
|
||
date_counts = df.groupby('Date').size().reset_index(name='Count')
|
||
fig = px.line(date_counts, x='Date', y='Count', title='Scheduled Content Over Time')
|
||
st.plotly_chart(fig, use_container_width=True)
|
||
|
||
# Content type distribution
|
||
type_counts = df['Type'].value_counts()
|
||
fig = px.bar(x=type_counts.index, y=type_counts.values, title='Content Type Distribution')
|
||
st.plotly_chart(fig, use_container_width=True)
|
||
|
||
# Status breakdown
|
||
status_counts = df['Status'].value_counts()
|
||
fig = px.pie(values=status_counts.values, names=status_counts.index, title='Status Distribution')
|
||
st.plotly_chart(fig, use_container_width=True)
|
||
|
||
# Performance metrics
|
||
st.subheader("📊 Performance Metrics")
|
||
|
||
col1, col2, col3 = st.columns(3)
|
||
|
||
with col1:
|
||
total_schedules = len(all_schedules)
|
||
st.metric("Total Schedules", total_schedules)
|
||
|
||
with col2:
|
||
completed_schedules = len([s for s in all_schedules if s.status == ScheduleStatus.COMPLETED])
|
||
success_rate = (completed_schedules / total_schedules * 100) if total_schedules > 0 else 0
|
||
st.metric("Success Rate", f"{success_rate:.1f}%")
|
||
|
||
with col3:
|
||
failed_schedules = len([s for s in all_schedules if s.status == ScheduleStatus.FAILED])
|
||
failure_rate = (failed_schedules / total_schedules * 100) if total_schedules > 0 else 0
|
||
st.metric("Failure Rate", f"{failure_rate:.1f}%") |