128 lines
3.2 KiB
Python
128 lines
3.2 KiB
Python
"""
|
|
Pipeline control — retry / force-node / pause / resume / state.
|
|
|
|
Wraps an in-memory engine (set by main.py) and emits events on the bus
|
|
so the UI live stream picks them up.
|
|
"""
|
|
|
|
from fastapi import APIRouter
|
|
from pydantic import BaseModel
|
|
from typing import Optional, Any
|
|
|
|
from backend.api.api import bus
|
|
from backend.events.schema import EventType
|
|
|
|
router = APIRouter(prefix="/api/pipeline", tags=["pipeline"])
|
|
|
|
# In-memory engine reference (set by main.py)
|
|
_ENGINE: Optional[Any] = None
|
|
_PIPELINE_STATE: dict = {
|
|
"state": "idle", # idle | running | paused | complete | failed
|
|
"active_actions": [],
|
|
"completed": 0,
|
|
"failed": 0,
|
|
"queued": 0,
|
|
"build_id": None,
|
|
}
|
|
|
|
|
|
class ActionBody(BaseModel):
|
|
action: str
|
|
node: Optional[str] = None
|
|
|
|
|
|
class ForceBody(BaseModel):
|
|
action: str
|
|
node: str
|
|
|
|
|
|
def set_engine(engine):
|
|
global _ENGINE
|
|
_ENGINE = engine
|
|
|
|
|
|
def get_state_snapshot():
|
|
return dict(_PIPELINE_STATE)
|
|
|
|
|
|
# -----------------------------
|
|
# GET STATE
|
|
# -----------------------------
|
|
@router.get("/state")
|
|
def get_state():
|
|
return get_state_snapshot()
|
|
|
|
|
|
# -----------------------------
|
|
# RETRY ACTION
|
|
# -----------------------------
|
|
@router.post("/retry")
|
|
def retry_action(body: ActionBody):
|
|
bus.emit(
|
|
EventType.PIPELINE_UPDATE,
|
|
action=body.action,
|
|
node=body.node,
|
|
state="retrying",
|
|
reason="manual_retry",
|
|
)
|
|
# In a real engine this would re-queue the action.
|
|
# For now we just record the intent.
|
|
return {"status": "retry_queued", "action": body.action, "node": body.node}
|
|
|
|
|
|
# -----------------------------
|
|
# FORCE NODE
|
|
# -----------------------------
|
|
@router.post("/force-node")
|
|
def force_node(body: ForceBody):
|
|
bus.emit(
|
|
EventType.PIPELINE_UPDATE,
|
|
action=body.action,
|
|
node=body.node,
|
|
state="forced",
|
|
reason="manual_force",
|
|
)
|
|
return {"status": "forced", "action": body.action, "node": body.node}
|
|
|
|
|
|
# -----------------------------
|
|
# PAUSE
|
|
# -----------------------------
|
|
@router.post("/pause")
|
|
def pause():
|
|
_PIPELINE_STATE["state"] = "paused"
|
|
if _ENGINE and hasattr(_ENGINE, "pause"):
|
|
_ENGINE.pause()
|
|
bus.emit(EventType.PIPELINE_UPDATE, state="paused", reason="manual_pause")
|
|
return {"status": "paused"}
|
|
|
|
|
|
# -----------------------------
|
|
# RESUME
|
|
# -----------------------------
|
|
@router.post("/resume")
|
|
def resume():
|
|
_PIPELINE_STATE["state"] = "running"
|
|
if _ENGINE and hasattr(_ENGINE, "resume"):
|
|
_ENGINE.resume()
|
|
bus.emit(EventType.PIPELINE_UPDATE, state="running", reason="manual_resume")
|
|
return {"status": "running"}
|
|
|
|
|
|
# -----------------------------
|
|
# Legacy wrapper for api.py facade
|
|
# -----------------------------
|
|
def pipeline_control_endpoint(action, payload=None, bus=None):
|
|
"""Legacy dispatcher — `action` is one of: retry, force, pause, resume, state."""
|
|
if action == "retry":
|
|
return retry_action(ActionBody(**(payload or {})))
|
|
elif action == "force":
|
|
return force_node(ForceBody(**(payload or {})))
|
|
elif action == "pause":
|
|
return pause()
|
|
elif action == "resume":
|
|
return resume()
|
|
elif action == "state":
|
|
return get_state_snapshot()
|
|
return {"error": f"unknown action: {action}"}
|