Files
manage/backend/src/media_library_viewer_api/routers/tasks.py
T
Developer 50eb76a10d feat(tasks): unify saved tasks on ssh_tasks services
- Add shared task_runner.run_saved_task helper used by routers/tasks.py and
  widgets/sources.py SshTaskWidgetSource.
- Saved tasks now target ssh_tasks service instances via default_service_id;
  the legacy default_machine_id and saved_task_runs are removed.
- Actions page lists ssh_tasks services for default and run-time selection.
- Update types, API client, hooks, tests, docs, and changelog.

Backend tests: 222 passed. Frontend lint/build/test: clean (71 passed).
2026-06-23 16:46:46 +00:00

130 lines
4.4 KiB
Python

"""Saved server actions/tasks router."""
from __future__ import annotations
import logging
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field
from media_library_viewer_api.dependencies import get_settings_store
from media_library_viewer_api.services.settings_store import SettingsStore
from media_library_viewer_api.services.task_runner import run_saved_task
from media_library_viewer_api.widgets.sources import ServiceRecord, build_service_record
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/tasks", tags=["tasks"])
class TaskInput(BaseModel):
id: str | None = None
name: str = Field(default="")
task_type: str = Field(default="shell", description="shell or python")
content: str = Field(default="")
enabled: bool = True
default_service_id: str = ""
notes: str = ""
class RunTaskRequest(BaseModel):
task_id: str
def _service_label(service: dict[str, Any] | None) -> str:
if not service:
return ""
return str(service.get("name") or service.get("id") or "")
def _resolve_service_for_task(
store: SettingsStore,
task: dict[str, Any],
service_id: str | None,
) -> dict[str, Any] | None:
if service_id:
return store.get_service(service_id)
default_service_id = str(task.get("default_service_id") or "").strip()
if default_service_id:
return store.get_service(default_service_id)
services = [svc for svc in store.list_services("ssh_tasks") if svc.get("enabled")]
return services[0] if services else None
def _service_row_to_record(service_row: dict[str, Any]) -> ServiceRecord:
"""Build a ServiceRecord from a raw settings_store service row."""
from media_library_viewer_api.services.settings_store import get_settings_store
return build_service_record(get_settings_store(), service_row)
@router.get("")
def list_tasks(store: SettingsStore = Depends(get_settings_store)) -> list[dict[str, Any]]:
return store.list_tasks()
@router.post("", status_code=status.HTTP_201_CREATED)
def create_task(task: TaskInput, store: SettingsStore = Depends(get_settings_store)) -> dict[str, Any]:
return store.upsert_task(task.model_dump(exclude_none=True), task.id)
@router.put("/{task_id}")
def update_task(task_id: str, task: TaskInput, store: SettingsStore = Depends(get_settings_store)) -> dict[str, Any]:
if not store.get_task(task_id):
raise HTTPException(status_code=404, detail="Task not found")
return store.upsert_task(task.model_dump(exclude_none=True), task_id)
@router.delete("/{task_id}")
def delete_task(task_id: str, store: SettingsStore = Depends(get_settings_store)) -> dict[str, str]:
if not store.get_task(task_id):
raise HTTPException(status_code=404, detail="Task not found")
store.delete_task(task_id)
return {"status": "deleted"}
@router.get("/{task_id}/runs")
def list_task_runs(
task_id: str,
limit: int = Query(default=10, ge=1, le=50),
store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
if not store.get_task(task_id):
raise HTTPException(status_code=404, detail="Task not found")
runs = store.list_service_task_runs(task_id=task_id, limit=limit)
return {"items": runs, "total": len(runs)}
@router.post("/run")
def run_task(
request: RunTaskRequest,
service_id: str | None = Query(default=None),
store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
task = store.get_task(request.task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
if not task.get("enabled", True):
raise HTTPException(status_code=400, detail="Task is disabled")
service_row = _resolve_service_for_task(store, task, service_id)
if not service_row:
raise HTTPException(status_code=400, detail="No SSH task service is available for this action")
if not service_row.get("enabled", True):
raise HTTPException(status_code=400, detail="Selected SSH task service is disabled")
service = _service_row_to_record(service_row)
result = run_saved_task(store, task, service)
return {
"task_id": task["id"],
"task_name": task["name"],
"service_id": service.id,
"service_name": _service_label(service_row),
"task_type": task.get("task_type", "shell"),
"exit_status": result.exit_status,
"stdout": result.stdout,
"stderr": result.stderr,
}