diff --git a/posadmesh-tools/flasher/app.js b/posadmesh-tools/flasher/app.js index 714335d3..689cb209 100644 --- a/posadmesh-tools/flasher/app.js +++ b/posadmesh-tools/flasher/app.js @@ -4,6 +4,7 @@ const $ = (id) => document.getElementById(id); const logEl = $('log'); let index = null; +let manifestStamp = ''; // generated_at манифеста, который сейчас в памяти let selected = null; // выбранный файл из index.json let ownFile = null; // файл, загруженный пользователем с компьютера let transport = null; @@ -23,6 +24,36 @@ function log(msg, cls = '') { logEl.scrollTop = logEl.scrollHeight; } +// ---------------------------------------------------------------- устаревшая страница + +// Страница, открытая давно, держит манифест в памяти: после выкладки новой +// сборки его ссылки ведут на удалённые файлы и скачивание молча не работает. +// Показываем баннер и предлагаем обновить страницу. +function showStale(msg) { + const box = $('stale'); + if (!box) return; + if (msg) $('stale-text').textContent = msg; + if (box.hidden) { + box.hidden = false; + log('Доступна новая версия прошивальщика — обновите страницу.', 'warn'); + } +} + +// Сверяет generated_at манифеста в памяти со свежим ответом сервера. +async function checkFreshness() { + if (!manifestStamp) return false; + try { + const res = await fetch('index.json', { cache: 'no-store' }); + if (!res.ok) return false; + const fresh = await res.json(); + if (fresh.generated_at && fresh.generated_at !== manifestStamp) { + showStale(); + return true; + } + } catch (e) { /* сеть недоступна — молчим, это не ошибка страницы */ } + return false; +} + function setProgress(ratio, label = '') { const pct = Math.max(0, Math.min(1, ratio || 0)) * 100; $('bar').style.width = pct.toFixed(1) + '%'; @@ -75,12 +106,25 @@ function fileRow(section, board, file) { const btn = document.createElement('button'); btn.className = 'small' + (file.flashable ? '' : ''); btn.textContent = file.flashable ? 'Прошить' : 'Скачать'; - btn.addEventListener('click', (e) => { + btn.addEventListener('click', async (e) => { e.preventDefault(); if (file.flashable) { select(file, row); flash(); } else { + // Перед скачиванием проверяем, что файл ещё на месте: у давно открытой + // страницы в памяти может быть старый манифест со ссылками на удалённые + // файлы. Тогда честно показываем баннер «обновите страницу». + btn.disabled = true; + try { + const head = await fetch(file.url, { method: 'HEAD', cache: 'no-store' }); + if (!head.ok) { + showStale('Файл ' + file.name + ' не найден — на сервере уже новая версия прошивальщика.'); + await checkFreshness(); + return; + } + } catch (err) { /* сеть недоступна — пробуем скачать как раньше */ } + finally { btn.disabled = false; } const a = document.createElement('a'); a.href = file.url; a.download = file.name; @@ -212,6 +256,7 @@ async function loadIndex() { const res = await fetch('index.json', { cache: 'no-store' }); if (!res.ok) throw new Error('HTTP ' + res.status); index = await res.json(); + manifestStamp = index.generated_at || ''; const host = $('sections'); host.textContent = ''; index.sections.forEach((s) => host.appendChild(sectionBlock(s))); @@ -479,3 +524,10 @@ if ('serial' in navigator) { loadIndex(); updateButtons(); + +// Раз в минуту и при возврате на вкладку сверяем манифест: если на сервере уже +// новая сборка, показываем баннер вместо тихих 404 при скачивании файлов. +setInterval(checkFreshness, 60000); +document.addEventListener('visibilitychange', () => { + if (!document.hidden) checkFreshness(); +}); diff --git a/posadmesh-tools/flasher/index.html b/posadmesh-tools/flasher/index.html index 18a8946f..94b7b246 100644 --- a/posadmesh-tools/flasher/index.html +++ b/posadmesh-tools/flasher/index.html @@ -5,7 +5,7 @@ Прошивальщик MeshCore и PosadMesh - +
@@ -23,6 +23,10 @@
+

Что означают файлы

@@ -153,6 +157,6 @@
- + diff --git a/posadmesh-tools/flasher/style.css b/posadmesh-tools/flasher/style.css index 8556649a..94991a98 100644 --- a/posadmesh-tools/flasher/style.css +++ b/posadmesh-tools/flasher/style.css @@ -197,3 +197,8 @@ details.verblock.newest > summary { background: #14241c; } .drop.filled { border-style: solid; border-color: #2f6f4f; color: #cdebd8; } .drop input[type=file] { display: none; } .hint { font-size: 13px; color: #7d8ea6; line-height: 1.45; } + +/* --- устаревшая страница прошивальщика --- */ +.stale { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 14px; padding: 12px 14px; border: 1px solid #7a5b1f; border-radius: 10px; background: rgba(122,91,31,.18); color: #f0d9a8; } +.stale[hidden] { display: none; } +.stale button { flex: 0 0 auto; }