87 lines
2.7 KiB
Python
87 lines
2.7 KiB
Python
|
|
"""Admin Scheduler dashboard: a live view of what the background scheduler is doing —
|
||
|
|
per-job run activity (from the in-process scheduler), the work still queued (DB-derived),
|
||
|
|
and the shared quota picture."""
|
||
|
|
from fastapi import APIRouter, Depends
|
||
|
|
from sqlalchemy import and_, func, or_, select
|
||
|
|
from sqlalchemy.orm import Session
|
||
|
|
|
||
|
|
from app import quota, state
|
||
|
|
from app.config import settings
|
||
|
|
from app.db import get_db
|
||
|
|
from app.models import Channel, Subscription, User, Video
|
||
|
|
from app.routes.admin import admin_user
|
||
|
|
from app.scheduler import scheduler_snapshot
|
||
|
|
from app.sync.runner import estimate_deep_backfill
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/api/admin/scheduler", tags=["admin"])
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("")
|
||
|
|
def get_scheduler(
|
||
|
|
_: User = Depends(admin_user), db: Session = Depends(get_db)
|
||
|
|
) -> dict:
|
||
|
|
snap = scheduler_snapshot()
|
||
|
|
|
||
|
|
# Work still queued (shared catalog, so these are instance-wide, not per-user).
|
||
|
|
deep_requested = (
|
||
|
|
select(Subscription.id)
|
||
|
|
.where(
|
||
|
|
Subscription.channel_id == Channel.id,
|
||
|
|
Subscription.deep_requested.is_(True),
|
||
|
|
)
|
||
|
|
.exists()
|
||
|
|
)
|
||
|
|
deep_channels = (
|
||
|
|
db.execute(
|
||
|
|
select(Channel).where(Channel.backfill_done.is_(False), deep_requested)
|
||
|
|
)
|
||
|
|
.scalars()
|
||
|
|
.all()
|
||
|
|
)
|
||
|
|
deep_eta = estimate_deep_backfill(db, deep_channels)
|
||
|
|
|
||
|
|
queue = {
|
||
|
|
"channels_recent_pending": db.scalar(
|
||
|
|
select(func.count()).select_from(Channel).where(Channel.recent_synced_at.is_(None))
|
||
|
|
)
|
||
|
|
or 0,
|
||
|
|
"channels_deep_pending": deep_eta["channels_pending"],
|
||
|
|
"deep_eta_seconds": deep_eta["eta_seconds"],
|
||
|
|
"videos_pending_enrich": db.scalar(
|
||
|
|
select(func.count()).select_from(Video).where(Video.enriched_at.is_(None))
|
||
|
|
)
|
||
|
|
or 0,
|
||
|
|
"videos_pending_shorts": db.scalar(
|
||
|
|
select(func.count())
|
||
|
|
.select_from(Video)
|
||
|
|
.where(Video.shorts_probed.is_(False), Video.enriched_at.is_not(None))
|
||
|
|
)
|
||
|
|
or 0,
|
||
|
|
"videos_live_refresh": db.scalar(
|
||
|
|
select(func.count())
|
||
|
|
.select_from(Video)
|
||
|
|
.where(
|
||
|
|
or_(
|
||
|
|
Video.live_status.in_(("live", "upcoming")),
|
||
|
|
and_(Video.live_status == "was_live", Video.duration_seconds.is_(None)),
|
||
|
|
)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
or 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
used = quota.units_used_today(db)
|
||
|
|
quota_info = {
|
||
|
|
"used_today": used,
|
||
|
|
"remaining_today": quota.remaining_today(db),
|
||
|
|
"daily_budget": settings.quota_daily_budget,
|
||
|
|
"backfill_reserve": settings.backfill_quota_reserve,
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
**snap,
|
||
|
|
"paused": state.is_sync_paused(db),
|
||
|
|
"queue": queue,
|
||
|
|
"quota": quota_info,
|
||
|
|
}
|