Files
strepsiades 4a9c444b10 first commit
2026-03-09 16:31:42 +08:00

156 lines
5.2 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Any
from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import FileResponse
from pydantic import BaseModel
from sync_state_machine.common.persistence import PersistenceBackend
from .config_models import PipelineUiConfig, list_ui_presets, preset_ui_config, file_ui_config
from sync_state_machine.config import get_preset_file_candidates
from .runner import PipelineRunnerService
class LogsResponse(BaseModel):
chunks: list[str]
next_index: int
class SqlQueryRequest(BaseModel):
sql: str
limit: int = 200
class SqlQueryResponse(BaseModel):
columns: list[str]
rows: list[dict[str, Any]]
error: str = ""
class LoadFileRequest(BaseModel):
path: str
def create_app() -> FastAPI:
app = FastAPI(title="Sync State Machine UI", version="0.1.0")
service = PipelineRunnerService()
web_root = Path(__file__).resolve().parent / "web"
@app.get("/")
async def index() -> FileResponse:
return FileResponse(str(web_root / "index.html"))
@app.get("/api/config/default", response_model=PipelineUiConfig)
async def get_default_config() -> PipelineUiConfig:
return service.get_default_config()
@app.get("/api/config/presets")
async def get_presets() -> dict:
data = []
for name in list_ui_presets():
data.append(
{
"name": name,
"candidates": [str(p) for p in get_preset_file_candidates(name)],
}
)
return {"presets": data}
@app.get("/api/config/preset/{preset_name}", response_model=PipelineUiConfig)
async def get_preset_config(preset_name: str) -> PipelineUiConfig:
try:
return preset_ui_config(preset_name)
except Exception as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/api/config/load-file", response_model=PipelineUiConfig)
async def load_config_file(payload: LoadFileRequest) -> PipelineUiConfig:
try:
return file_ui_config(payload.path)
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.get("/api/config/current", response_model=PipelineUiConfig)
async def get_current_config() -> PipelineUiConfig:
return service.get_config()
@app.get("/api/config/visualized")
async def get_visualized_config() -> dict:
cfg = service.get_config()
return cfg.model_dump()
@app.post("/api/config/current", response_model=PipelineUiConfig)
async def set_current_config(config: PipelineUiConfig) -> PipelineUiConfig:
try:
return service.set_config(config)
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.get("/api/status")
async def get_status() -> dict:
status = service.get_status()
return {
"running": status.running,
"started_at": status.started_at,
"ended_at": status.ended_at,
"last_error": status.last_error,
"last_stats": status.last_stats or {},
"current_log_file": status.current_log_file,
}
@app.post("/api/run")
async def run_pipeline(config: PipelineUiConfig) -> dict:
try:
status = await service.start(config)
return {
"running": status.running,
"started_at": status.started_at,
"current_log_file": status.current_log_file,
}
except Exception as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
@app.post("/api/stop")
async def stop_pipeline() -> dict:
status = await service.stop()
return {
"running": status.running,
"ended_at": status.ended_at,
"last_error": status.last_error,
}
@app.get("/api/logs", response_model=LogsResponse)
async def get_logs(since: int = Query(default=0, ge=0)) -> LogsResponse:
chunks, next_idx = service.get_logs(since)
return LogsResponse(chunks=chunks, next_index=next_idx)
@app.get("/api/records/summary")
async def get_records_summary() -> dict:
cfg = service.get_config()
return PersistenceBackend.records_summary(
backend=cfg.persist.backend,
db_path=str(Path(cfg.persist.db_path)),
)
@app.post("/api/records/query", response_model=SqlQueryResponse)
async def run_records_query(payload: SqlQueryRequest) -> SqlQueryResponse:
sql = (payload.sql or "").strip()
if not sql:
return SqlQueryResponse(columns=[], rows=[], error="SQL is empty")
cfg = service.get_config()
try:
columns, rows = PersistenceBackend.query_records(
backend=cfg.persist.backend,
db_path=str(Path(cfg.persist.db_path)),
sql=sql,
limit=payload.limit,
)
return SqlQueryResponse(columns=columns, rows=rows, error="")
except Exception as exc:
return SqlQueryResponse(columns=[], rows=[], error=f"{type(exc).__name__}: {exc}")
return app