diff --git a/scripts/patch_upload_navigation_guard.py b/scripts/patch_upload_navigation_guard.py new file mode 100644 index 0000000..6f14fb8 --- /dev/null +++ b/scripts/patch_upload_navigation_guard.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys + +app_dir = Path(sys.argv[1]).resolve().parent if len(sys.argv) > 1 else Path('/opt/virtuality/web') +templates_dir = app_dir / 'templates' +changed = [] + +guard_script = r''' + + function setUploadNavigationGuard(active) { + window.virtualityUploadActive = Boolean(active); + document.body.classList.toggle('upload-locked', window.virtualityUploadActive); + } + + window.addEventListener('beforeunload', function (event) { + if (!window.virtualityUploadActive) return; + event.preventDefault(); + event.returnValue = 'Идёт загрузка файла. Переход остановит передачу.'; + return event.returnValue; + }); + + document.addEventListener('click', function (event) { + if (!window.virtualityUploadActive) return; + const target = event.target.closest('a, button, input[type="submit"]'); + if (!target) return; + if (target.id === 'iso-upload-button' || target.id === 'disk-upload-button') return; + event.preventDefault(); + event.stopPropagation(); + alert('Идёт загрузка файла. Дождись завершения или нажми «Отменить загрузку». Переход по меню сейчас заблокирован, чтобы файл не оборвался.'); + }, true); + + document.addEventListener('submit', function (event) { + if (!window.virtualityUploadActive) return; + if (event.target && (event.target.id === 'iso-upload-form' || event.target.id === 'disk-upload-form')) return; + event.preventDefault(); + event.stopPropagation(); + alert('Идёт загрузка файла. Дождись завершения или нажми «Отменить загрузку».'); + }, true); +''' + + +def patch_template(path: Path, active_token: str, form_id: str) -> None: + if not path.exists(): + return + text = path.read_text() + original = text + + if 'function setUploadNavigationGuard(active)' not in text: + marker = ' function bytesText(bytes) {' + if marker not in text: + raise SystemExit(f'bytesText marker not found in {path}') + text = text.replace(marker, guard_script + '\n' + marker, 1) + + text = text.replace(' uploadActive = active;\n', ' uploadActive = active;\n setUploadNavigationGuard(active);\n') + + # During server-side post-processing after upload, file transfer is done, so page navigation is safe again. + text = text.replace(" window.location.href = '/iso';", " setUploadNavigationGuard(false);\n window.location.href = '/iso';") + text = text.replace(" window.location.href = '/disk-images';", " setUploadNavigationGuard(false);\n window.location.href = '/disk-images';") + + # Conversion polling is not an active browser upload anymore; allow navigation while backend operation continues. + text = text.replace(" setUploadMode(false);\n fileInput.disabled = true;", " setUploadMode(false);\n setUploadNavigationGuard(false);\n fileInput.disabled = true;") + + if text != original: + path.write_text(text) + changed.append(str(path)) + +patch_template(templates_dir / 'iso.html', 'iso-upload-button', 'iso-upload-form') +patch_template(templates_dir / 'disk_images.html', 'disk-upload-button', 'disk-upload-form') + +print('upload navigation guard patch applied:') +for item in changed or ['already applied']: + print(f'- {item}')