Add config snapshot export and treat finished cycles as success.

Per-NE failures stay in fail_count for retry; completed cycles no longer fail the whole run. Configurations page can download txt/zip snapshots.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-07-31 11:02:07 +08:00
parent abb5172a8f
commit 3fc80f55bc
7 changed files with 203 additions and 38 deletions

View file

@ -2,13 +2,17 @@
from __future__ import annotations
from urllib.parse import quote
from fastapi import APIRouter, BackgroundTasks, Depends, Query, Request
from fastapi.responses import Response
from sqlalchemy.orm import Session
from .auth_service import write_audit
from .config_sync_runner import dispatch_cycle
from .config_sync_schemas import ConfigSyncCycleCreate, ConfigSyncPolicyUpdate
from .config_sync_service import (
build_snapshot_export,
create_cycle,
dashboard,
get_cycle,
@ -147,6 +151,28 @@ def api_list_snapshots(
)
@router.get("/snapshots/{source}/{target_id}/download")
def api_download_snapshot(
source: str,
target_id: str,
field: str = Query(default="primary"),
db: Session = Depends(get_db),
):
filename, payload, media_type = build_snapshot_export(db, source, target_id, field=field)
# ASCII fallback + UTF-8 filename for CJK device names
safe_ascii = filename.encode("ascii", errors="replace").decode("ascii").replace("?", "_")
return Response(
content=payload,
media_type=media_type,
headers={
"content-disposition": (
f'attachment; filename="{safe_ascii}"; '
f"filename*=UTF-8''{quote(filename)}"
)
},
)
@router.get("/snapshots/{source}/{target_id}")
def api_get_snapshot(
source: str,