v0.0.66: блокировка выбора/удаления файлов на время загрузки и обработки, заморозка сессии после результата + кнопка Новая сессия
Deploy drhider / validate (push) Canceled after 0s

This commit is contained in:
“Naeel”
2026-08-24 15:42:37 +03:00
parent dd9785747d
commit c346be5add
2 changed files with 36 additions and 4 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ if _sys_path_root not in sys.path:
sys.path.insert(0, _sys_path_root) sys.path.insert(0, _sys_path_root)
# Версия приложения (меняется при изменениях) # Версия приложения (меняется при изменениях)
VERSION = "0.0.65" VERSION = "0.0.66"
def setup_logging(): def setup_logging():
+35 -3
View File
@@ -73,6 +73,7 @@
padding: 2px 4px; font-size: 14px; line-height: 1; padding: 2px 4px; font-size: 14px; line-height: 1;
} }
.remove-btn:hover { color: var(--red); } .remove-btn:hover { color: var(--red); }
body.busy .remove-btn { display: none; }
.footer-bar { .footer-bar {
margin-top: 10px; display: flex; justify-content: space-between; align-items: center; margin-top: 10px; display: flex; justify-content: space-between; align-items: center;
font-size: 12px; color: var(--muted); font-size: 12px; color: var(--muted);
@@ -162,6 +163,7 @@
<div class="footer-bar"> <div class="footer-bar">
<span id="fileCount">0 файлов</span> <span id="fileCount">0 файлов</span>
<button class="btn" id="cancelBtn" style="display:none" onclick="confirmCancel()">⏹ Прервать</button> <button class="btn" id="cancelBtn" style="display:none" onclick="confirmCancel()">⏹ Прервать</button>
<button class="btn" id="newSessionBtn" style="display:none" onclick="resetAll()">🔄 Новая сессия</button>
<button class="btn btn-primary" id="uploadBtn" disabled onclick="uploadFiles()">🛡️ Обфусцировать</button> <button class="btn btn-primary" id="uploadBtn" disabled onclick="uploadFiles()">🛡️ Обфусцировать</button>
</div> </div>
<div class="status" id="status"></div> <div class="status" id="status"></div>
@@ -214,6 +216,8 @@ let procExtractDone = false; // true после extract_done (далее done
let procExtractRate = null; // измеренная скорость извлечения (сек/МБ) для оценок ожидающих let procExtractRate = null; // измеренная скорость извлечения (сек/МБ) для оценок ожидающих
let procRefresh = null; // setInterval перерисовки таблицы let procRefresh = null; // setInterval перерисовки таблицы
let ptimer = null, liveRefresh = null; // таймеры статуса и live-блока let ptimer = null, liveRefresh = null; // таймеры статуса и live-блока
let busy = false; // идёт загрузка/обработка — список файлов заблокирован
let sessionDone = false; // результат готов — сессия заморожена до «Новая сессия»
const MAX_FILE_BYTES = 50 * 1024 * 1024; // 50 МБ на один файл const MAX_FILE_BYTES = 50 * 1024 * 1024; // 50 МБ на один файл
const MAX_SESSION_BYTES = 500 * 1024 * 1024; // 500 МБ суммарно на сессию const MAX_SESSION_BYTES = 500 * 1024 * 1024; // 500 МБ суммарно на сессию
const VM_UPLOAD_URL = 'https://contracts.kube5s.ru/drhider-upload/'; // ВМ-буфер: PUT больших файлов (шлюз кластера их рвёт) const VM_UPLOAD_URL = 'https://contracts.kube5s.ru/drhider-upload/'; // ВМ-буфер: PUT больших файлов (шлюз кластера их рвёт)
@@ -238,6 +242,9 @@ function resetAll() {
procNameIdx = {}; procNameIdx = {};
procExtractDone = false; procExtractDone = false;
procExtractRate = null; procExtractRate = null;
sessionDone = false;
setBusy(false);
document.getElementById('newSessionBtn').style.display = 'none';
document.getElementById('cancelBtn').style.display = 'none'; document.getElementById('cancelBtn').style.display = 'none';
fi.value = ''; fi.value = '';
rr(); rr();
@@ -375,7 +382,13 @@ function finishProcUI() {
rr(); rr();
} }
function rm(i) { const nm = sf[i].name; overNames.delete(nm); fileMeta.delete(nm); sf.splice(i, 1); const d = new DataTransfer(); sf.forEach(f => d.items.add(f)); fi.files = d.files; rr(); } function setBusy(b) {
busy = b;
fi.disabled = b; // «Выбрать файлы» — блокируется на время загрузки/обработки и в замороженной сессии
document.body.classList.toggle('busy', b); // скрывает кнопки «✕» в таблице
}
function rm(i) { if (busy) return; const nm = sf[i].name; overNames.delete(nm); fileMeta.delete(nm); sf.splice(i, 1); const d = new DataTransfer(); sf.forEach(f => d.items.add(f)); fi.files = d.files; rr(); }
window.addEventListener('load', () => { sf = []; fileMeta = new Map(); fi.value = ''; rr(); /* прогрев upstream-соединения */ fetch('/health').catch(() => {}); }); window.addEventListener('load', () => { sf = []; fileMeta = new Map(); fi.value = ''; rr(); /* прогрев upstream-соединения */ fetch('/health').catch(() => {}); });
// ═══════════ Нативная распаковка ZIP (без внешних библиотек) ═══════════ // ═══════════ Нативная распаковка ZIP (без внешних библиотек) ═══════════
@@ -516,6 +529,7 @@ function addFileWithDedup(file) {
} }
fi.addEventListener('change', async () => { fi.addEventListener('change', async () => {
if (busy) return; // во время загрузки/обработки менять список нельзя
const incoming = Array.from(fi.files); const incoming = Array.from(fi.files);
const hasZip = incoming.some(f => f.name.toLowerCase().endsWith('.zip')); const hasZip = incoming.some(f => f.name.toLowerCase().endsWith('.zip'));
if (hasZip) { if (hasZip) {
@@ -553,7 +567,11 @@ fi.addEventListener('change', async () => {
function ss(idx, h) { const e = document.getElementById('st-' + idx); if (e) e.innerHTML = h; } function ss(idx, h) { const e = document.getElementById('st-' + idx); if (e) e.innerHTML = h; }
async function uploadFiles() { async function uploadFiles() {
if (busy) return; // уже идёт загрузка/обработка
if (sf.length === 0) return; if (sf.length === 0) return;
setBusy(true);
sessionDone = false;
document.getElementById('newSessionBtn').style.display = 'none';
// Сохранить список до очистки // Сохранить список до очистки
const files = sf.slice(); const files = sf.slice();
// Только учитываемые файлы идут на загрузку/обфускацию (переборные пропускаем) // Только учитываемые файлы идут на загрузку/обфускацию (переборные пропускаем)
@@ -574,7 +592,7 @@ async function uploadFiles() {
document.getElementById('liveBlock').classList.remove('show'); document.getElementById('liveBlock').classList.remove('show');
ub.disabled = true; ub.disabled = true;
const total = toSend.length; const total = toSend.length;
if (total === 0) { st.className = 'status error'; st.textContent = 'Нет файлов для обфускации (все превышают лимит).'; ub.disabled = false; return; } if (total === 0) { st.className = 'status error'; st.textContent = 'Нет файлов для обфускации (все превышают лимит).'; setBusy(false); ub.disabled = false; return; }
// Фаза 1: загрузка на ВМ-буфер (PUT напрямую на nginx ВМ, минуя шлюз кластера) // Фаза 1: загрузка на ВМ-буфер (PUT напрямую на nginx ВМ, минуя шлюз кластера)
const token = crypto.randomUUID(); const token = crypto.randomUUID();
@@ -619,6 +637,7 @@ async function uploadFiles() {
ss(i, '<span style="color:#ef4444">✗</span>'); ss(i, '<span style="color:#ef4444">✗</span>');
st.className = 'status error'; st.className = 'status error';
st.textContent = 'Ошибка загрузки на ВМ: ' + err.message; st.textContent = 'Ошибка загрузки на ВМ: ' + err.message;
setBusy(false);
ub.disabled = false; ub.disabled = false;
return; return;
} }
@@ -640,6 +659,7 @@ async function uploadFiles() {
} catch (err) { } catch (err) {
st.className = 'status error'; st.className = 'status error';
st.textContent = 'Ошибка передачи ссылок: ' + err.message; st.textContent = 'Ошибка передачи ссылок: ' + err.message;
setBusy(false);
ub.disabled = false; ub.disabled = false;
return; return;
} }
@@ -648,6 +668,7 @@ async function uploadFiles() {
if (TEST_UPLOAD_ONLY) { if (TEST_UPLOAD_ONLY) {
st.className = 'status done'; st.className = 'status done';
st.textContent = '✅ Загружено ' + uploadedCount + ' файлов (тест: обработка пропущена). Сессия: ' + currentSid; st.textContent = '✅ Загружено ' + uploadedCount + ' файлов (тест: обработка пропущена). Сессия: ' + currentSid;
setBusy(false);
ub.disabled = false; ub.disabled = false;
return; return;
} }
@@ -806,6 +827,11 @@ async function uploadFiles() {
} }
sb.classList.add('show'); sb.classList.add('show');
db.classList.add('show'); db.classList.add('show');
// Заморозка сессии: результат готов (частичный), список файлов больше не меняем
sessionDone = true;
ub.disabled = true;
fi.disabled = true;
document.getElementById('newSessionBtn').style.display = 'inline-block';
resolve(); resolve();
}); });
activeES.addEventListener('complete', function(e) { activeES.addEventListener('complete', function(e) {
@@ -826,6 +852,11 @@ async function uploadFiles() {
} }
sb.classList.add('show'); sb.classList.add('show');
db.classList.add('show'); db.classList.add('show');
// Заморозка сессии: результат готов, список файлов больше не меняем
sessionDone = true;
ub.disabled = true;
fi.disabled = true;
document.getElementById('newSessionBtn').style.display = 'inline-block';
resolve(); resolve();
}); });
activeES.onerror = function() { activeES.onerror = function() {
@@ -835,10 +866,11 @@ async function uploadFiles() {
}); });
} catch (err) { } catch (err) {
finishProcUI(); finishProcUI();
setBusy(false);
st.className = 'status error'; st.className = 'status error';
st.textContent = 'Ошибка: ' + err.message; st.textContent = 'Ошибка: ' + err.message;
} }
ub.disabled = false; if (!sessionDone) ub.disabled = false;
} }
function downloadZip() { function downloadZip() {