Интегрирован wesp в сайт
CI / quality (push) Canceled after 0s

This commit is contained in:
влад
2026-07-17 12:57:18 +03:00
parent 5dfa06ddbe
commit 355c0ef9f1
883 changed files with 194576 additions and 177 deletions
+406
View File
@@ -0,0 +1,406 @@
/**
* Фоновый poll статуса обновлений + баннер «Обновить / Позже».
* WespUpdateNotifier.init({ dialog: WespDialog | WespKioskDialog });
*/
(function (global) {
var DISMISS_KEY = "wesp_update_dismissed";
var DEFAULT_POLL_MS = 300000;
var PROGRESS_POLL_MS = 500;
var opts = {};
var pollTimer = null;
var progressTimer = null;
var lastStatus = null;
var lastProgress = null;
function ensureDom() {
if (document.getElementById("wespUpdateBanner")) return;
var banner = document.createElement("div");
banner.id = "wespUpdateBanner";
banner.setAttribute("role", "region");
banner.setAttribute("aria-label", "Доступно обновление");
banner.innerHTML =
'<div class="wesp-update-banner-inner">' +
'<div class="wesp-update-banner-text" id="wespUpdateBannerText"></div>' +
'<div class="wesp-update-banner-changelog" id="wespUpdateBannerChangelog"></div>' +
'<div class="wesp-update-banner-actions">' +
'<button type="button" class="wesp-update-banner-btn" id="wespUpdateBannerDetails">Подробнее</button>' +
'<button type="button" class="wesp-update-banner-btn" id="wespUpdateBannerLater">Позже</button>' +
'<button type="button" class="wesp-update-banner-btn wesp-update-banner-btn--primary" id="wespUpdateBannerInstall">Обновить</button>' +
"</div></div>";
document.body.appendChild(banner);
var overlay = document.createElement("div");
overlay.id = "wespUpdateOverlay";
overlay.setAttribute("aria-hidden", "true");
overlay.innerHTML =
'<div class="wesp-update-overlay-panel" role="alertdialog" aria-modal="true">' +
'<h2 class="wesp-update-overlay-title" id="wespUpdateOverlayTitle">Идёт обновление</h2>' +
'<p class="wesp-update-overlay-body" id="wespUpdateOverlayBody">Подготовка…</p>' +
'<div class="wesp-update-progress-wrap" id="wespUpdateProgressWrap">' +
'<div class="wesp-update-progress-track">' +
'<div class="wesp-update-progress-bar" id="wespUpdateProgressBar"></div>' +
"</div>" +
'<div class="wesp-update-progress-meta">' +
'<span class="wesp-update-progress-percent" id="wespUpdateProgressPercent">0%</span>' +
'<span class="wesp-update-progress-detail" id="wespUpdateProgressDetail"></span>' +
"</div>" +
"</div>" +
"</div>";
document.body.appendChild(overlay);
document.getElementById("wespUpdateBannerLater").addEventListener("click", dismissBanner);
document.getElementById("wespUpdateBannerDetails").addEventListener("click", toggleDetails);
document.getElementById("wespUpdateBannerInstall").addEventListener("click", onInstallClick);
}
function isDismissed(version) {
try {
return sessionStorage.getItem(DISMISS_KEY) === String(version || "");
} catch (e) {
return false;
}
}
function dismissBanner() {
if (lastStatus && lastStatus.pending_version) {
try {
sessionStorage.setItem(DISMISS_KEY, String(lastStatus.pending_version));
} catch (e) {}
}
hideBanner();
}
function hideBanner() {
var el = document.getElementById("wespUpdateBanner");
if (el) {
el.classList.remove("wesp-update-banner--visible", "wesp-update-banner--expanded");
}
}
function showBanner(status) {
if (!status || !status.update_available || !status.pending_version) {
hideBanner();
return;
}
if (isDismissed(status.pending_version)) {
hideBanner();
return;
}
ensureDom();
var el = document.getElementById("wespUpdateBanner");
var text = document.getElementById("wespUpdateBannerText");
var changelog = document.getElementById("wespUpdateBannerChangelog");
if (!el || !text) return;
text.innerHTML =
"Доступна версия <strong>" +
escapeHtml(status.pending_version) +
"</strong>" +
(status.pending_name ? " — " + escapeHtml(status.pending_name) : "") +
". Сейчас: <strong>" +
escapeHtml(status.current_version || "—") +
"</strong>.";
if (changelog) {
changelog.textContent = status.pending_body || "";
changelog.style.display = status.pending_body ? "" : "none";
}
var detailsBtn = document.getElementById("wespUpdateBannerDetails");
if (detailsBtn) {
detailsBtn.style.display = status.pending_body ? "" : "none";
}
el.classList.add("wesp-update-banner--visible");
}
function toggleDetails() {
var el = document.getElementById("wespUpdateBanner");
if (el) el.classList.toggle("wesp-update-banner--expanded");
}
function showInstallingOverlay(message, progress) {
ensureDom();
var overlay = document.getElementById("wespUpdateOverlay");
if (overlay) {
overlay.classList.add("wesp-update-overlay--visible");
overlay.setAttribute("aria-hidden", "false");
}
if (message) {
var bodyEl = document.getElementById("wespUpdateOverlayBody");
if (bodyEl) bodyEl.textContent = message;
}
applyProgressUi(progress);
hideBanner();
}
function hideInstallingOverlay() {
var overlay = document.getElementById("wespUpdateOverlay");
if (overlay) {
overlay.classList.remove("wesp-update-overlay--visible");
overlay.setAttribute("aria-hidden", "true");
}
}
function applyProgressUi(progress) {
if (!progress || typeof progress !== "object") return;
lastProgress = progress;
var titleEl = document.getElementById("wespUpdateOverlayTitle");
var bodyEl = document.getElementById("wespUpdateOverlayBody");
var barEl = document.getElementById("wespUpdateProgressBar");
var pctEl = document.getElementById("wespUpdateProgressPercent");
var detailEl = document.getElementById("wespUpdateProgressDetail");
var pct = Number(progress.percent);
if (!Number.isFinite(pct)) pct = 0;
pct = Math.max(0, Math.min(100, Math.round(pct)));
if (titleEl && progress.target_version) {
titleEl.textContent = "Обновление до версии " + progress.target_version;
}
if (bodyEl && progress.message) {
bodyEl.textContent = progress.message;
}
if (barEl) {
barEl.style.width = pct + "%";
barEl.setAttribute("aria-valuenow", String(pct));
}
if (pctEl) pctEl.textContent = pct + "%";
if (detailEl) {
detailEl.textContent = progress.detail || "";
detailEl.style.display = progress.detail ? "" : "none";
}
}
function stopProgressPolling() {
if (progressTimer) {
clearInterval(progressTimer);
progressTimer = null;
}
}
function startProgressPolling() {
stopProgressPolling();
progressTimer = setInterval(function () {
fetchStatus().then(function (status) {
if (!status) return;
if (status.update_progress) {
applyProgressUi(status.update_progress);
} else if (status.is_updating) {
applyProgressUi({
message: "Выполняется обновление…",
percent: lastProgress ? lastProgress.percent : 5,
});
}
});
}, PROGRESS_POLL_MS);
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function fetchStatus() {
var url = opts.statusUrl || "/api/updates/status";
return fetch(url, { credentials: "same-origin", cache: "no-store" }).then(function (r) {
if (r.status === 401 || r.status === 403) return null;
return r.json().catch(function () {
return null;
});
});
}
function applyStatus(status) {
if (!status || typeof status !== "object") return;
lastStatus = status;
if (status.update_available && status.pending_version) {
try {
var dismissed = sessionStorage.getItem(DISMISS_KEY);
if (dismissed && dismissed !== String(status.pending_version)) {
sessionStorage.removeItem(DISMISS_KEY);
}
} catch (e) {}
}
showBanner(status);
}
function pollOnce() {
return fetchStatus().then(function (status) {
applyStatus(status);
});
}
function onInstallClick() {
var dialog = opts.dialog;
if (!dialog || typeof dialog.confirm !== "function") {
if (!global.confirm("Система перезапустится. Продолжить?")) return;
doInstall();
return;
}
var msg =
"Будет загружена и установлена версия " +
(lastStatus && lastStatus.pending_version ? lastStatus.pending_version : "") +
".\n\nСистема перезапустится. Продолжить?";
dialog
.confirm(msg, {
title: "Обновление WESP",
okText: "Обновить",
cancelText: "Отмена",
danger: true,
})
.then(function (ok) {
if (ok) doInstall();
});
}
function waitForServerAndReload() {
var url = opts.statusUrl || "/api/updates/status";
var attempts = 0;
var maxAttempts = 90;
var intervalMs = 2000;
showInstallingOverlay(
"Обновление установлено. Ожидаем перезапуск сервера — страница обновится автоматически.",
{ message: "Ожидание перезапуска сервера…", percent: 100, detail: null }
);
function scheduleNext() {
if (attempts >= maxAttempts) {
var bodyEl = document.getElementById("wespUpdateOverlayBody");
if (bodyEl) {
bodyEl.innerHTML =
"Сервер долго не отвечает. Если перезапуск уже завершился, " +
'<button type="button" class="wesp-update-banner-btn wesp-update-banner-btn--primary" id="wespUpdateManualReload">Обновить страницу</button>';
var btn = document.getElementById("wespUpdateManualReload");
if (btn) btn.addEventListener("click", function () { global.location.reload(); });
}
return;
}
setTimeout(tick, intervalMs);
}
function showRollbackOverlay(state) {
var ver = (state && (state.previous_version || state.message)) || "—";
var msg =
(state && state.message) ||
"Обновление отменено. Восстановлена предыдущая версия.";
showInstallingOverlay(msg, {
message: msg,
percent: 100,
target_version: ver,
});
var bar = document.getElementById("wespUpdateProgressBar");
var panel = document.querySelector("#wespUpdateOverlay .wesp-update-overlay-panel");
if (bar) bar.classList.add("wesp-update-progress-bar--error");
if (panel) panel.classList.add("wesp-update-overlay-panel--rollback");
var bodyEl = document.getElementById("wespUpdateOverlayBody");
if (bodyEl) {
bodyEl.innerHTML =
escapeHtml(msg) +
' <button type="button" class="wesp-update-banner-btn wesp-update-banner-btn--primary" id="wespUpdateManualReload">Обновить страницу</button>';
var btn = document.getElementById("wespUpdateManualReload");
if (btn) btn.addEventListener("click", function () { global.location.reload(); });
}
}
function tick() {
attempts += 1;
applyProgressUi({
message: "Ожидание перезапуска сервера…",
percent: 100,
detail: "~" + attempts * 2 + " с",
});
fetch(url, { credentials: "same-origin", cache: "no-store" })
.then(function (r) {
return r.json().catch(function () { return null; }).then(function (body) {
return { ok: r.ok, status: r.status, body: body };
});
})
.then(function (res) {
var st = res.body && res.body.last_update_state;
if (st && st.status === "rolled_back") {
showRollbackOverlay(st);
return;
}
if (res.ok || res.status === 401 || res.status === 403) {
global.location.reload();
return;
}
scheduleNext();
})
.catch(function () {
scheduleNext();
});
}
setTimeout(tick, 3000);
}
function doInstall() {
var targetVersion = lastStatus && lastStatus.pending_version ? lastStatus.pending_version : "";
showInstallingOverlay("Подготовка к обновлению…", {
message: "Подготовка к обновлению…",
percent: 0,
target_version: targetVersion,
});
startProgressPolling();
var url = opts.installUrl || "/api/updates/install";
fetch(url, { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: "{}" })
.then(function (r) {
return r.json().catch(function () {
return {};
}).then(function (body) {
return { ok: r.ok, body: body };
});
})
.then(function (res) {
stopProgressPolling();
if (!res.ok) {
hideInstallingOverlay();
var errMsg = (res.body && (res.body.error || res.body.message)) || "Не удалось установить обновление";
if (opts.dialog && opts.dialog.alert) {
opts.dialog.alert(errMsg, { title: "Ошибка обновления", variant: "danger" });
} else {
global.alert(errMsg);
}
pollOnce();
return;
}
waitForServerAndReload();
})
.catch(function () {
stopProgressPolling();
hideInstallingOverlay();
if (opts.dialog && opts.dialog.alert) {
opts.dialog.alert("Сетевая ошибка при установке обновления.", { title: "Ошибка", variant: "danger" });
}
});
}
function init(options) {
opts = options || {};
if (!opts.dialog) {
opts.dialog = global.WespDialog || global.WespKioskDialog || null;
}
ensureDom();
pollOnce();
var interval = Number(opts.pollIntervalMs);
if (!Number.isFinite(interval) || interval < 60000) interval = DEFAULT_POLL_MS;
if (pollTimer) clearInterval(pollTimer);
pollTimer = setInterval(pollOnce, interval);
}
global.WespUpdateNotifier = {
init: init,
pollOnce: pollOnce,
dismissBanner: dismissBanner,
};
})(typeof window !== "undefined" ? window : globalThis);