feat: sync execution with 3s progress polling (stage 2)

- pulp_client: get_repo / sync_repo / get_task (비동기 task href 반환)
- POST /repos/{uuid}/sync: remote 확인 후 동기화 시작, 진행률 조각 반환
- GET /repos/{uuid}/progress: progress_reports 합산 → 바/퍼센트,
  완료/실패 시 hx-trigger 제거로 폴링 중단
- task_store: uuid→task_href 메모리 추적 (단일 인스턴스), 대시보드 '동기화 중' 집계
- views.task_progress: state/done/total/percent + created_resources에서 새 vN
- 동기화는 안전 동작이라 확인 모달 없이 실행 (스펙 §5-1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:09:37 +09:00
parent c1192ab09d
commit 6c41b89c53
14 changed files with 441 additions and 12 deletions

View File

@@ -69,12 +69,47 @@ def build_repo_view(
}
def summarize(repo_views: list[dict[str, Any]]) -> dict[str, int]:
"""상단 요약 카드 수치."""
def summarize(repo_views: list[dict[str, Any]], syncing: int = 0) -> dict[str, int]:
"""상단 요약 카드 수치. syncing 은 task_store.active_count() 에서 주입."""
return {
"total": len(repo_views),
# TODO(단계2): 진행 중 sync task 수를 메모리 task 맵에서 집계
"syncing": 0,
"syncing": syncing,
"gpg_pass": sum(1 for r in repo_views if r["gpg"]["level"] == "pass"),
"total_packages": sum(r["package_count"] or 0 for r in repo_views),
}
def task_progress(task: dict[str, Any]) -> dict[str, Any]:
"""Pulp task → 진행률 표시 모델.
progress_reports[] 의 done/total 을 합산해 퍼센트를 낸다. 완료 시
created_resources 에서 새 RepositoryVersion 번호(vN)를 추출한다.
"""
state = task.get("state", "waiting")
reports = task.get("progress_reports") or []
done = sum((r or {}).get("done") or 0 for r in reports)
total = sum((r or {}).get("total") or 0 for r in reports)
percent = int(done / total * 100) if total else 0
new_version = None
for href in task.get("created_resources") or []:
if "/versions/" in href:
new_version = version_number(href)
break
error = None
if state == "failed":
err = task.get("error") or {}
error = err.get("description") or "동기화 작업이 실패했습니다."
return {
"state": state,
"done": done,
"total": total,
"percent": percent,
"new_version": new_version,
"error": error,
"completed": state == "completed",
"failed": state == "failed",
"running": state in ("waiting", "running"),
}