v0.0.49: индикатор распаковки архивов, разделение фаз (загрузка/обработка), таймауты SSE на ingress 1800с
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
# v0.0.49 — индикатор распаковки, разделение фаз, таймауты SSE — 2026-08-19
|
||||||
|
|
||||||
|
**Дата:** 2026-08-19
|
||||||
|
**Версия:** 0.0.48 → 0.0.49
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
Отзыв тестировщика:
|
||||||
|
1. «После Выбрать файлы долго не респондит — разбирает архивы» → нет индикатора.
|
||||||
|
2. «По Обфусцировать — двойной проход по файлам» → фазы загрузки и обработки не разделены.
|
||||||
|
3. «Ошибка: SSE connection failed» → долгое SSE рвалось шлюзом.
|
||||||
|
|
||||||
|
## Изменения
|
||||||
|
|
||||||
|
### Ingress (kubectl patch, не код)
|
||||||
|
- `proxy-read-timeout: 600 → 1800`
|
||||||
|
- `proxy-send-timeout: 600 → 1800`
|
||||||
|
— шлюз больше не рвёт долгое SSE (было ~10 мин, стало 30 мин).
|
||||||
|
|
||||||
|
### `site/templates/index.html`
|
||||||
|
- **Индикатор распаковки**: при выборе файлов с `.zip` — `cursor: wait` +
|
||||||
|
статус «Разбираю архивы…», снимается после распаковки (try/finally).
|
||||||
|
- **Разделение фаз**:
|
||||||
|
- Фаза 1: статус «Загрузка (этап 1/2) i/N: имя».
|
||||||
|
- Фаза 2: статус «Обработка (этап 2/2)…», таймер «Обработка (этап 2/2)… Nс».
|
||||||
|
- При старте фазы 2 загрузочные статусы сбрасываются на «⏳».
|
||||||
|
|
||||||
|
### `site/app.py`
|
||||||
|
- `VERSION = "0.0.49"`.
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
- `node --check`, `py_compile` — OK.
|
||||||
|
- Тесты builder/replacer/scanner/zip — все зелёные.
|
||||||
|
|
||||||
|
## Примечание
|
||||||
|
- Причина «SSE connection failed»: шлюз рвал долгое SSE (proxy-read-timeout 600с).
|
||||||
|
Таймауты подняты до 1800с. Если обработка > 30 мин — нужно ещё выше или
|
||||||
|
авто-реконнект EventSource (отложено: реконнект вызывает повторную обработку).
|
||||||
+1
-1
@@ -20,7 +20,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.48"
|
VERSION = "0.0.49"
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|||||||
+28
-12
@@ -343,16 +343,31 @@ function addFileWithDedup(file) {
|
|||||||
|
|
||||||
fi.addEventListener('change', async () => {
|
fi.addEventListener('change', async () => {
|
||||||
const incoming = Array.from(fi.files);
|
const incoming = Array.from(fi.files);
|
||||||
for (const f of incoming) {
|
const hasZip = incoming.some(f => f.name.toLowerCase().endsWith('.zip'));
|
||||||
if (f.name.toLowerCase().endsWith('.zip')) {
|
if (hasZip) {
|
||||||
try {
|
// Распаковка архивов может занять время — показать индикатор
|
||||||
const nested = await listZipFiles(f);
|
document.body.style.cursor = 'wait';
|
||||||
nested.forEach(x => addFileWithDedup(x));
|
st.className = 'status progress';
|
||||||
} catch (err) {
|
st.textContent = 'Разбираю архивы…';
|
||||||
addFileWithDedup(f); // не удалось распаковать — добавить zip как есть
|
}
|
||||||
|
try {
|
||||||
|
for (const f of incoming) {
|
||||||
|
if (f.name.toLowerCase().endsWith('.zip')) {
|
||||||
|
try {
|
||||||
|
const nested = await listZipFiles(f);
|
||||||
|
nested.forEach(x => addFileWithDedup(x));
|
||||||
|
} catch (err) {
|
||||||
|
addFileWithDedup(f); // не удалось распаковать — добавить zip как есть
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addFileWithDedup(f);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
addFileWithDedup(f);
|
} finally {
|
||||||
|
if (hasZip) {
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
st.className = '';
|
||||||
|
st.textContent = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const d = new DataTransfer();
|
const d = new DataTransfer();
|
||||||
@@ -383,7 +398,7 @@ async function uploadFiles() {
|
|||||||
for (let i = 0; i < total; i++) {
|
for (let i = 0; i < total; i++) {
|
||||||
const f = files[i], n = i + 1;
|
const f = files[i], n = i + 1;
|
||||||
st.className = 'status progress';
|
st.className = 'status progress';
|
||||||
st.textContent = 'Загрузка ' + n + '/' + total + ': ' + f.name;
|
st.textContent = 'Загрузка (этап 1/2) ' + n + '/' + total + ': ' + f.name;
|
||||||
try {
|
try {
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
@@ -431,15 +446,16 @@ async function uploadFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Фаза 2: обработка (SSE — прогресс по каждому файлу)
|
// Фаза 2: обработка (SSE — прогресс по каждому файлу)
|
||||||
|
// Сброс загрузочных статусов — теперь этап обработки
|
||||||
for (let i = 0; i < total; i++) ss(i, '<span style="color:#2563eb">⏳</span>');
|
for (let i = 0; i < total; i++) ss(i, '<span style="color:#2563eb">⏳</span>');
|
||||||
const fileTimers = {}; // idx -> performance.now()
|
const fileTimers = {}; // idx -> performance.now()
|
||||||
const fileIntervals = {}; // idx -> setInterval id
|
const fileIntervals = {}; // idx -> setInterval id
|
||||||
st.className = 'status progress';
|
st.className = 'status progress';
|
||||||
st.textContent = 'Обработка...';
|
st.textContent = 'Обработка (этап 2/2)…';
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
const ptimer = setInterval(() => {
|
const ptimer = setInterval(() => {
|
||||||
const sec = Math.round((performance.now() - t0) / 1000);
|
const sec = Math.round((performance.now() - t0) / 1000);
|
||||||
st.textContent = 'Обработка... ' + sec + 'с';
|
st.textContent = 'Обработка (этап 2/2)… ' + sec + 'с';
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Крупный live-блок «Идёт обработка» с тикающим таймером
|
// Крупный live-блок «Идёт обработка» с тикающим таймером
|
||||||
|
|||||||
Reference in New Issue
Block a user