feat(backups): restore target node/storage selection + task progress tracking
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 1m6s
Deploy to Production / Trigger production update (push) Successful in 8s

Add target node and storage overrides to single and bulk restore flows, and
track Proxmox task progress (parse_task_progress in the monitoring worker,
new ProxmoxTask progress column + migration, enriched task API responses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 21:26:01 +05:00
parent fbf6d174b6
commit 681aa2f303
10 changed files with 234 additions and 25 deletions

View File

@@ -4,14 +4,35 @@ Uses APScheduler for periodic tasks
"""
import asyncio
import re
from datetime import datetime, timedelta, timezone
from typing import Dict, Any, List
from typing import Dict, Any, List, Optional
from loguru import logger
from ..config import utcnow
# qmrestore / vma extract emit lines like "progress 47% (read ...)".
# Some restores instead print bare "47% ..." markers. Capture either form.
_PROGRESS_RE = re.compile(r"(?:progress\s+)?(\d{1,3})\s*%")
def parse_task_progress(log_text: Optional[str]) -> Optional[int]:
"""Extract the latest restore/backup progress percentage from a task log.
Returns the last percentage seen (0-100), or None when the log has none.
"""
if not log_text:
return None
matches = _PROGRESS_RE.findall(log_text)
if not matches:
return None
# The log is appended in order, so the last match is the most recent progress.
pct = int(matches[-1])
return max(0, min(100, pct))
try:
from backend.app.db import SessionLocal
from backend.app.models import User, ProxmoxServer, PanelSettings, VMInstance, IPAMAllocation, Notification, ProxmoxTask
@@ -968,6 +989,10 @@ class MonitoringWorker:
entry.get("t", "") if isinstance(entry, dict) else str(entry)
for entry in log_entries
)
# Extract a live progress % from the log (qmrestore etc.)
parsed = parse_task_progress(task.log_text)
if parsed is not None:
task.progress = parsed
except Exception:
pass
@@ -977,6 +1002,10 @@ class MonitoringWorker:
task.exit_status = exit_status
task.status = "completed" if exit_status == "OK" else "failed"
task.completed_at = utcnow()
# A successfully finished task is 100% done even if the last
# log line didn't print a percentage.
if exit_status == "OK" and task.progress is not None:
task.progress = 100
logger.info(f"[UPID SYNC] Task #{task.id} finished: {task.status} ({exit_status})")
db.commit()