50eb76a10d
- 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).
130 lines
4.4 KiB
Python
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,
|
|
}
|