# retoor import logging from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse from devplacepy.database import db, get_admin_uids, get_int_setting, get_users_by_uids from devplacepy.responses import action_result, respond from devplacepy.schemas import AdminDeviiTasksOut from devplacepy.seo import base_seo_context, site_url, website_schema from devplacepy.services.audit import record as audit from devplacepy.services.devii import config as devii_config from devplacepy.services.devii.tasks import limits from devplacepy.services.devii.tasks.guards import DEFAULT_MAX_PER_OWNER from devplacepy.services.devii.tasks.schedule import now_utc from devplacepy.services.devii.tasks.store import TABLE, TaskStore from devplacepy.utils import not_found, require_admin logger = logging.getLogger(__name__) router = APIRouter() STATES = ("active", "inactive", "all") def _schedule_text(row: dict) -> str: kind = row.get("kind") or "" if kind == "interval": return f"every {row.get('every_seconds')}s" if kind == "cron": return f"cron {row.get('cron')}" return f"once {row.get('run_at') or ''}".strip() def _rows(state: str) -> list[dict]: if TABLE not in db.tables: return [] criteria: dict = {"deleted_at": None} if state == "active": criteria["enabled"] = True elif state == "inactive": criteria["enabled"] = False rows = list(db[TABLE].find(**criteria)) rows.sort(key=lambda row: int(row.get("run_count") or 0), reverse=True) return rows def _quotas(owner_uids: set[str]) -> dict[str, dict]: reference = now_utc() quotas = {} for owner_uid in owner_uids: runs = limits.run_quota(db, "user", owner_uid, reference) creations = limits.create_quota(db, "user", owner_uid, reference) quotas[owner_uid] = { "runs_used": runs.used, "runs_limit": runs.limit, "creates_used": creations.used, "creates_limit": creations.limit, } return quotas def _items(rows: list[dict]) -> list[dict]: owners = get_users_by_uids([row.get("owner_id") for row in rows if row.get("owner_id")]) admins = get_admin_uids() quotas = _quotas({str(row.get("owner_id") or "") for row in rows if row.get("owner_id")}) items = [] for row in rows: owner_uid = row.get("owner_id") or "" owner = owners.get(owner_uid) items.append( { "uid": row.get("uid"), "label": row.get("label") or row.get("uid"), "owner_uid": owner_uid, "owner": owner["username"] if owner else owner_uid, "owner_is_admin": owner_uid in admins, "quota": quotas.get(owner_uid, {}), "schedule": _schedule_text(row), "status": row.get("status") or "", "enabled": bool(row.get("enabled")), "run_count": int(row.get("run_count") or 0), "max_runs": row.get("max_runs"), "failure_count": int(row.get("failure_count") or 0), "next_run_at": row.get("next_run_at"), "expires_at": row.get("expires_at"), "last_error": row.get("last_error"), } ) return items def _require_task(uid: str) -> dict: row = db[TABLE].find_one(uid=uid, deleted_at=None) if TABLE in db.tables else None if row is None: raise not_found("Unknown task") return row @router.get("/devii-tasks", response_class=HTMLResponse) async def admin_devii_tasks(request: Request, state: str = "active"): admin = require_admin(request) if state not in STATES: state = "active" rows = _rows(state) items = _items(rows) bounds = { "max_concurrent": get_int_setting( devii_config.FIELD_TASK_MAX_CONCURRENT, devii_config.DEFAULT_TASK_MAX_CONCURRENT, ), "max_per_owner": get_int_setting( devii_config.FIELD_TASK_MAX_PER_OWNER, DEFAULT_MAX_PER_OWNER ), "max_failures": get_int_setting( devii_config.FIELD_TASK_MAX_FAILURES, devii_config.DEFAULT_TASK_MAX_FAILURES, ), "idle_days": get_int_setting( devii_config.FIELD_TASK_IDLE_DAYS, devii_config.DEFAULT_TASK_IDLE_DAYS ), "member_create_24h": limits.create_limit(False), "member_runs_24h": limits.run_limit(False), "admin_create_24h": limits.create_limit(True), "admin_runs_24h": limits.run_limit(True), } tabs = [ {"key": key, "label": key.capitalize(), "active": key == state} for key in STATES ] base = site_url(request) seo_ctx = base_seo_context( request, title="Devii tasks - Admin", description="Every scheduled Devii task, its owner, and its bounds.", breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "Admin", "url": "/admin"}, {"name": "Devii tasks", "url": "/admin/devii-tasks"}, ], schemas=[website_schema(base)], ) return respond( request, "admin_devii_tasks.html", { **seo_ctx, "request": request, "user": admin, "items": items, "state": state, "tabs": tabs, "limits": bounds, "admin_section": "devii-tasks", }, model=AdminDeviiTasksOut, ) @router.post("/devii-tasks/{uid}/disable") async def admin_devii_task_disable(request: Request, uid: str): admin = require_admin(request) row = _require_task(uid) store = TaskStore(db, str(row.get("owner_kind") or "user"), str(row.get("owner_id") or "")) store.update( uid, { "enabled": False, "status": "disabled", "next_run_at": None, "last_error": f"disabled by {admin['username']}", }, ) logger.info(f"Admin {admin['username']} disabled Devii task {uid}") audit.record( request, "admin.devii_task.disable", user=admin, target_type="task", target_uid=uid, target_label=row.get("label"), metadata={"owner_id": row.get("owner_id"), "run_count": row.get("run_count")}, summary=f"{admin['username']} disabled Devii task {uid}", ) return action_result(request, "/admin/devii-tasks") @router.post("/devii-tasks/{uid}/delete") async def admin_devii_task_delete(request: Request, uid: str): admin = require_admin(request) row = _require_task(uid) store = TaskStore(db, str(row.get("owner_kind") or "user"), str(row.get("owner_id") or "")) store.delete(uid) logger.info(f"Admin {admin['username']} deleted Devii task {uid}") audit.record( request, "admin.devii_task.delete", user=admin, target_type="task", target_uid=uid, target_label=row.get("label"), metadata={"owner_id": row.get("owner_id"), "run_count": row.get("run_count")}, summary=f"{admin['username']} deleted Devii task {uid}", ) return action_result(request, "/admin/devii-tasks")