прошивальщик: баннер и проверка свежести — не отдаём ссылки на удалённые файлы из старой вкладки

This commit is contained in:
shade committed 2026-10-18 09:29:01 +03:00
1 parent b8d38db198
commit a4e80805d6
3 files changed
+64 -3

No files matched your search

+53 -1
View File
@@ -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();
});
+6 -2
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Прошивальщик MeshCore и PosadMesh</title>
<meta name="description" content="Прошивка плат MeshCore и MQTT-репитеров PosadMesh прямо из браузера">
<link rel="stylesheet" href="style.css?v=20261016">
<link rel="stylesheet" href="style.css?v=20261018">
</head>
<body>
<div class="wrap">
@@ -23,6 +23,10 @@
<div class="layout">
<main>
<div class="stale" id="stale" hidden>
<span id="stale-text">На сервере уже новая версия прошивальщика — список файлов на этой странице устарел.</span>
<button type="button" class="small" onclick="location.reload()">Обновить страницу</button>
</div>
<div class="notes" style="margin-top:0;border-top:none;padding-top:0">
<h2>Что означают файлы</h2>
<div class="cards">
@@ -153,6 +157,6 @@
</footer>
</div>
<script type="module" src="app.js?v=20261016"></script>
<script type="module" src="app.js?v=20261018"></script>
</body>
</html>
+5
View File
@@ -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; }