Операции, прерванные перезапуском панели, закрываются при старте; снимки: исправления по ревью

- core.interrupt_orphaned_operations() при запуске панели, active_operations_for() по всем задачам
- снимки: имена с пробелами, снимки вне панели без кнопок, --force только на «revert requires force»,
  память на паузе учитывается, защита от двойного нажатия, надпись «Создаём снимок…»

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LEUXMcdZo7b87mjkUsPgg
Этот коммит содержится в:
Claude
2026-09-25 15:05:49 +00:00
родитель 2b7223f311
Коммит eb7b71f478
6 изменённых файлов: 96 добавлений и 10 удалений
+35
Просмотреть файл
@@ -271,6 +271,41 @@ def list_operations(limit: int = 25) -> list[dict[str, Any]]:
return operations
def interrupt_orphaned_operations() -> int:
"""Operations run in threads of the panel process: after a restart the ones still marked
queued/running will never finish. Close them so they do not block the next attempt."""
if not OPERATIONS_DIR.exists():
return 0
count = 0
for path in OPERATIONS_DIR.glob("*.json"):
try:
data = json.loads(path.read_text())
except (OSError, ValueError):
continue
if data.get("status") not in ("queued", "running"):
continue
data.pop("log_tail", None)
message = "Прервано: панель перезапускалась во время операции. Проверьте состояние машины и запустите действие снова."
update_operation(data, status="error", progress=100, message=message, finished_at=utc_now(), interrupted=True)
append_operation_log(data["id"], message)
count += 1
return count
def active_operations_for(vm_name: str, kinds: tuple[str, ...] | set[str] | None = None) -> list[dict[str, Any]]:
"""Unfinished operations of one machine, over the whole operations directory (not only the latest N)."""
ensure_operations_dir()
found = []
for path in OPERATIONS_DIR.glob("*.json"):
try:
data = json.loads(path.read_text())
except (OSError, ValueError):
continue
if data.get("vm_name") == vm_name and data.get("status") in ("queued", "running") and (kinds is None or data.get("type") in kinds):
found.append(data)
return found
def running_operations(kind: str | None = None) -> list[dict[str, Any]]:
return [op for op in list_operations(50) if op.get("status") in ("queued", "running") and (kind is None or op.get("type") == kind)]