mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 04:20:45 +08:00
Add config sync cycle stop and surface pause/stop controls in the UI.
Cancel remaining tasks on stop and keep finalize from overwriting a cancelled cycle. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
559cc14d2c
commit
1c8d512701
6 changed files with 124 additions and 5 deletions
|
|
@ -24,6 +24,7 @@ from .config_sync_service import (
|
|||
list_snapshots,
|
||||
pause_cycle,
|
||||
resume_cycle,
|
||||
stop_cycle,
|
||||
update_policy,
|
||||
)
|
||||
from .db import get_db
|
||||
|
|
@ -137,6 +138,23 @@ def api_resume_cycle(
|
|||
return out.model_dump()
|
||||
|
||||
|
||||
@router.post("/cycles/{cycle_id}/stop")
|
||||
def api_stop_cycle(cycle_id: str, request: Request, db: Session = Depends(get_db)):
|
||||
out = stop_cycle(db, cycle_id)
|
||||
uid, uname = _actor(request)
|
||||
write_audit(
|
||||
db,
|
||||
action="config_sync.stop",
|
||||
actor_user_id=uid,
|
||||
actor_username=uname,
|
||||
method="POST",
|
||||
path=f"/v1/config-sync/cycles/{cycle_id}/stop",
|
||||
status_code=200,
|
||||
detail={"cycle_id": cycle_id},
|
||||
)
|
||||
return out.model_dump()
|
||||
|
||||
|
||||
@router.get("/snapshots")
|
||||
def api_list_snapshots(
|
||||
page: int = Query(default=1, ge=1),
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ def pause_cycle(db: Session, cycle_id: str) -> ConfigSyncCycleOut:
|
|||
row = db.get(ConfigSyncCycle, cycle_id)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="cycle_not_found")
|
||||
if str(row.status) != "running":
|
||||
if str(row.status) not in ("running", "pending"):
|
||||
raise HTTPException(status_code=400, detail="cycle_not_running")
|
||||
row.status = "paused"
|
||||
db.commit()
|
||||
|
|
@ -407,6 +407,45 @@ def resume_cycle(db: Session, cycle_id: str) -> ConfigSyncCycleOut:
|
|||
return cycle_to_out(row)
|
||||
|
||||
|
||||
def stop_cycle(db: Session, cycle_id: str) -> ConfigSyncCycleOut:
|
||||
"""Cancel remaining work and close the cycle (running/paused/pending)."""
|
||||
row = db.get(ConfigSyncCycle, cycle_id)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="cycle_not_found")
|
||||
if str(row.status) not in ("running", "paused", "pending"):
|
||||
raise HTTPException(status_code=400, detail="cycle_not_active")
|
||||
now = _utcnow()
|
||||
pending = (
|
||||
db.query(ConfigSyncTask)
|
||||
.filter(
|
||||
ConfigSyncTask.cycle_id == cycle_id,
|
||||
ConfigSyncTask.status.in_(("pending", "running")),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for task in pending:
|
||||
# In-flight workers may still finish and overwrite; pending must not start.
|
||||
if str(task.status) == "pending":
|
||||
task.status = "cancelled"
|
||||
task.message = "stopped_by_user"
|
||||
task.ended_at = now
|
||||
else:
|
||||
task.message = (str(task.message or "") + " · stop_requested")[:1020]
|
||||
row.status = "cancelled"
|
||||
row.error_message = "stopped_by_user"
|
||||
row.ended_at = now
|
||||
db.commit()
|
||||
sync_cycle_progress(db, cycle_id)
|
||||
db.refresh(row)
|
||||
try:
|
||||
from .config_sync_runner import _release_pool
|
||||
|
||||
_release_pool(cycle_id)
|
||||
except Exception:
|
||||
pass
|
||||
return cycle_to_out(row)
|
||||
|
||||
|
||||
def dashboard(db: Session) -> ConfigSyncDashboardOut:
|
||||
policy = ensure_policy(db)
|
||||
snap_count = int(db.query(func.count()).select_from(NeConfigSnapshot).scalar() or 0)
|
||||
|
|
@ -629,7 +668,7 @@ def finalize_cycle(db: Session, cycle_id: str) -> None:
|
|||
cycle = db.get(ConfigSyncCycle, cycle_id)
|
||||
if not cycle:
|
||||
return
|
||||
if str(cycle.status) == "paused":
|
||||
if str(cycle.status) in ("paused", "cancelled"):
|
||||
return
|
||||
pending = (
|
||||
db.query(func.count())
|
||||
|
|
@ -642,6 +681,8 @@ def finalize_cycle(db: Session, cycle_id: str) -> None:
|
|||
return
|
||||
sync_cycle_progress(db, cycle_id)
|
||||
db.refresh(cycle)
|
||||
if str(cycle.status) in ("paused", "cancelled"):
|
||||
return
|
||||
# Cycle outcome is about finishing the run, not per-NE results.
|
||||
# Individual task failures stay in fail_count for retry/dashboard.
|
||||
cycle.status = "success"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue