jarvis-cloud / backend /routes /automation_routes.py
Jarvis2345's picture
deploy(S4): Blender headless pipeline + WebAR client + backend fixes
9afc3bb verified
Raw
History Blame Contribute Delete
6.16 kB
import logging
import re
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
router = APIRouter()
class Workflow(BaseModel):
name: str
steps: list
@router.get("/list")
async def get_automation_list():
"""User automations plus the live scheduler jobs.
This used to be a hardcoded `return []`, so the endpoint reported success
while claiming the system had no automations — even though
automation_service.load_automations() reads real rows from
`custom_automations`, and the scheduler genuinely runs persisted jobs
(mcu_suit_vitals, mcu_battlefield_intel, hf_keepalive_ping) out of the
`apscheduler_jobs` table. Anything driven off this route saw an empty system.
"""
from backend.services.automation_service import load_automations, scheduler
try:
automations = load_automations()
except Exception as exc: # a missing table must not 500 the whole panel
automations = []
logging.warning("load_automations failed: %s", exc)
jobs = []
try:
for job in scheduler.get_jobs():
jobs.append({
"id": job.id,
"name": getattr(job, "name", job.id),
"trigger": str(job.trigger),
"next_run_time": job.next_run_time.isoformat() if job.next_run_time else None,
})
except Exception as exc:
logging.warning("scheduler.get_jobs failed: %s", exc)
return {"automations": automations, "scheduled_jobs": jobs,
"counts": {"automations": len(automations), "scheduled_jobs": len(jobs)}}
@router.post("/trigger/{id}")
async def trigger_automation_route(id: str):
from backend.services.automation_service import trigger_automation
await trigger_automation(id)
return {"status": "triggered"}
@router.post("/pause_all")
async def pause_all():
from backend.services.automation_service import pause_all_automations
await pause_all_automations()
return {"status": "paused"}
@router.post("/resume_all")
async def resume_all():
from backend.services.automation_service import resume_all_automations
await resume_all_automations()
return {"status": "resumed"}
@router.post("/jobs/{job_id}/pause")
async def pause_single_job(job_id: str):
from backend.services.automation_service import pause_job
# APScheduler raises JobLookupError for an unknown id. Unhandled, that
# surfaced as a 500 "Internal Server Error" for what is really a client
# asking about a job that does not exist — a 404.
try:
pause_job(job_id)
except Exception as exc:
if "No job by the id" in str(exc):
raise HTTPException(status_code=404, detail=f"No job with id {job_id}")
raise
return {"status": f"job {job_id} paused"}
@router.post("/jobs/{job_id}/resume")
async def resume_single_job(job_id: str):
from backend.services.automation_service import resume_job
try:
resume_job(job_id)
except Exception as exc:
if "No job by the id" in str(exc):
raise HTTPException(status_code=404, detail=f"No job with id {job_id}")
raise
return {"status": f"job {job_id} resumed"}
@router.delete("/jobs/{job_id}")
async def delete_single_job(job_id: str):
from backend.services.automation_service import delete_job
delete_job(job_id)
return {"status": f"job {job_id} deleted"}
@router.post("/save")
async def save_workflow(w: Workflow):
"""Persist a workflow.
This previously returned {"status": "saved"} without writing anything — the
Save action reported success and the workflow was gone on the next read.
automation_service.save_automation() is the real write path into the
`custom_automations` table that /list reads back.
"""
from backend.services.automation_service import save_automation
if not (w.name or "").strip():
raise HTTPException(status_code=400, detail="name is required")
auto_id = re.sub(r"[^a-z0-9]+", "-", w.name.strip().lower()).strip("-") or "workflow"
try:
save_automation(
auto_id=auto_id,
trigger_type="manual",
trigger_data={},
action_type="workflow",
action_data={"name": w.name, "steps": w.steps},
)
except Exception as exc:
logging.exception("save_automation failed")
raise HTTPException(status_code=500, detail=str(exc))
return {"status": "saved", "id": auto_id, "steps": len(w.steps)}
@router.get("/history")
async def get_history(limit: int = 100):
# Part 18: real backing store — automation_service.execute_action records
# every run (ok/error) into the automation_history table in the same
# env-driven SQLite file as custom_automations.
from backend.services.automation_service import load_history
try:
return load_history(limit)
except Exception as exc:
logging.exception("automation history read failed")
raise HTTPException(status_code=500, detail=str(exc))
@router.post("/dry-run")
async def dry_run_workflow(w: Workflow):
"""
Simulates workflow execution without side effects.
Returns a preview of the steps and their expected mock results.
"""
import asyncio
preview_steps = []
for i, step in enumerate(w.steps):
# Determine a mock result based on step type if present
step_type = step.get('type', 'unknown') if isinstance(step, dict) else 'unknown'
mock_result = f"Mock result for {step_type}"
if step_type == 'api_call': mock_result = "HTTP 200 OK (Mock)"
elif step_type == 'script': mock_result = "Script execution simulated"
elif step_type == 'agent': mock_result = "Agent reasoning simulated"
preview_steps.append({
"step_index": i,
"step_config": step,
"expected_status": "success",
"mock_result": mock_result
})
# Simulate minor delay
await asyncio.sleep(0.1)
return {
"status": "dry-run-complete",
"total_steps": len(w.steps),
"execution_preview": preview_steps
}