Add upload wait timer and progress explanation

This commit is contained in:
“Naeel”
2026-09-06 12:23:58 +03:00
parent 8768fd96ec
commit 87de22306f
6 changed files with 119 additions and 17 deletions
+79 -15
View File
@@ -30,6 +30,11 @@
</button>
<span id="upload-status" style="font: 14px sans-serif; color: #2d4a36;"></span>
</div>
<div id="upload-progress-explanation" class="upload-progress-explanation" hidden>
<strong id="upload-phase">Подготовка загрузки</strong>
<span id="upload-phase-help">Первое соединение может занять время: устанавливаются соединения с буфером ВМ и сервисом.</span>
<span id="upload-elapsed">Прошло: 00:00</span>
</div>
<div id="session-info" style="margin-top: 14px; font: 13px sans-serif; display: none;">
<strong>Сессия в RAM:</strong> <code id="session-id"></code>
<div id="session-files" style="margin-top: 6px; color: #46604e;"></div>
@@ -45,9 +50,41 @@
const sessionInfo = document.getElementById('session-info');
const sessionIdEl = document.getElementById('session-id');
const sessionFilesEl = document.getElementById('session-files');
const progressExplanation = document.getElementById('upload-progress-explanation');
const uploadPhase = document.getElementById('upload-phase');
const uploadPhaseHelp = document.getElementById('upload-phase-help');
const uploadElapsed = document.getElementById('upload-elapsed');
let abortCtrl = null;
let selectedFiles = [];
let uploadTimer = null;
let uploadStartedAt = 0;
const formatElapsed = (seconds) => {
const minutes = Math.floor(seconds / 60).toString().padStart(2, '0');
const remainder = (seconds % 60).toString().padStart(2, '0');
return `${minutes}:${remainder}`;
};
const stopUploadTimer = () => {
if (uploadTimer) window.clearInterval(uploadTimer);
uploadTimer = null;
};
const startUploadTimer = () => {
stopUploadTimer();
uploadStartedAt = Date.now();
progressExplanation.hidden = false;
uploadTimer = window.setInterval(() => {
uploadElapsed.textContent = `Прошло: ${formatElapsed(Math.floor((Date.now() - uploadStartedAt) / 1000))}`;
}, 1000);
};
const showUploadPhase = (phase, explanation) => {
progressExplanation.hidden = false;
uploadPhase.textContent = phase;
uploadPhaseHelp.textContent = explanation;
};
const picker = FilePicker.initFilePicker({
mount: '#file-picker',
@@ -67,30 +104,55 @@
cancelBtn.style.display = 'inline-block';
sessionInfo.style.display = 'none';
abortCtrl = new AbortController();
startUploadTimer();
showUploadPhase(
'Первый файл: устанавливаем соединение',
'Это не зависание. Сначала файл отправляется в буфер ВМ, затем сервис забирает его в RAM и очищает буфер.'
);
const vmUploadUrl = new URL({{ config.vmUploadUrl|tojson }}, window.location.origin).href;
const res = await FilePicker.uploadViaVM(files, {
vmUploadUrl: vmUploadUrl,
backendUploadUrl: '/api/upload_refs',
signal: abortCtrl.signal,
onStatus: (msg) => { uploadStatus.textContent = msg; },
onFileStatus: (k, statusText) => {
const row = document.querySelectorAll('.tree-row.tree-file')[k];
if (row) {
const statusCell = row.querySelectorAll('td')[2];
if (statusCell) statusCell.innerHTML = statusText;
let res;
try {
res = await FilePicker.uploadViaVM(files, {
vmUploadUrl: vmUploadUrl,
backendUploadUrl: '/api/upload_refs',
signal: abortCtrl.signal,
onStatus: (msg) => {
uploadStatus.textContent = msg;
if (msg.startsWith('Загрузка в буфер')) {
showUploadPhase('Отправка файла в буфер ВМ', 'Идёт PUT текущего файла. После этого сервис сразу перенесёт его в RAM.');
} else if (msg.startsWith('Приём сервисом')) {
showUploadPhase('Перенос файла в RAM', 'Сервис забирает текущий файл из буфера ВМ и удаляет его после успешного приёма.');
}
},
onFileStatus: (k, statusText) => {
const row = document.querySelectorAll('.tree-row.tree-file')[k];
if (row) {
const statusCell = row.querySelectorAll('td')[2];
if (statusCell) statusCell.innerHTML = statusText;
}
},
onFileComplete: (info) => {
console.log('File delivered to RAM:', info);
}
},
onFileComplete: (info) => {
console.log('File delivered to RAM:', info);
}
});
});
} catch (error) {
stopUploadTimer();
cancelBtn.style.display = 'none';
uploadBtn.disabled = selectedFiles.length === 0;
showUploadPhase('Загрузка остановлена', 'Произошла непредвиденная ошибка. Повторите загрузку.');
uploadStatus.innerHTML = '<span style="color:#b32f22; font-weight: bold;">✗ Ошибка загрузки</span>';
console.error('Upload failed unexpectedly', error);
return;
}
stopUploadTimer();
cancelBtn.style.display = 'none';
uploadBtn.disabled = selectedFiles.length === 0;
if (res.ok) {
showUploadPhase('Загрузка завершена', 'Все файлы доставлены в RAM сессии.');
uploadStatus.innerHTML = '<span style="color:#1f5a3b; font-weight: bold;">✓ Все ' + res.count + ' файл(ов) успешно доставлены в RAM бэкенда!</span>';
sessionIdEl.textContent = res.session;
sessionInfo.style.display = 'block';
@@ -106,8 +168,10 @@
console.error('Failed to load session files', e);
}
} else if (res.aborted) {
showUploadPhase('Загрузка отменена', 'Переданные до отмены файлы уже находятся в RAM сессии.');
uploadStatus.innerHTML = '<span style="color:#8c5148;">Загрузка отменена пользователем</span>';
} else {
showUploadPhase('Загрузка остановлена', 'Проверьте сообщение об ошибке. Уже доставленные файлы остаются в RAM сессии.');
uploadStatus.innerHTML = '<span style="color:#b32f22; font-weight: bold;">✗ Ошибка: ' + res.error + '</span>';
}
});