From 724011c73ce986a4ac88c84dfd12e14caec9e84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= <78488229+viktor138irk@users.noreply.github.com> Date: Thu, 7 May 2026 20:35:42 +0900 Subject: [PATCH] =?UTF-8?q?AJAX-=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D0=B1?= =?UTF-8?q?=D1=8B=D0=BB=D0=B0=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/static/panel.js | 162 ++------------------------------------------ 1 file changed, 6 insertions(+), 156 deletions(-) diff --git a/web/static/panel.js b/web/static/panel.js index ac44b4f..c6b0279 100644 --- a/web/static/panel.js +++ b/web/static/panel.js @@ -1,49 +1,11 @@ (() => { const app = { - loading: false, refreshTimers: [], }; - const fullLoadPages = ['/iso', '/disk-images', '/vm/create', '/update']; - function qs(selector, root = document) { return root.querySelector(selector); } function qsa(selector, root = document) { return Array.from(root.querySelectorAll(selector)); } - function sameOriginLink(link) { - if (!link || !link.href) return false; - const url = new URL(link.href, window.location.href); - return url.origin === window.location.origin; - } - - function requiresFullLoad(pathname) { - return fullLoadPages.some((path) => pathname === path || pathname.startsWith(path + '/')); - } - - function isAjaxSafeLink(link) { - if (!sameOriginLink(link)) return false; - const url = new URL(link.href, window.location.href); - if (requiresFullLoad(url.pathname)) return false; - if (url.pathname.startsWith('/vm/') && url.pathname.endsWith('/console')) return false; - if (url.pathname.startsWith('/static/')) return false; - if (url.pathname.startsWith('/api/')) return false; - if (link.target && link.target !== '_self') return false; - if (link.hasAttribute('download')) return false; - if (link.dataset.noAjax === '1') return false; - return true; - } - - function setLoading(active) { - app.loading = active; - document.documentElement.classList.toggle('ajax-loading', active); - let bar = qs('#ajax-progress'); - if (!bar) { - bar = document.createElement('div'); - bar.id = 'ajax-progress'; - document.body.appendChild(bar); - } - bar.classList.toggle('active', active); - } - function activeSidebar(pathname = window.location.pathname) { qsa('.v-nav a').forEach((item) => { const url = new URL(item.href, window.location.href); @@ -57,117 +19,6 @@ }); } - function stopRefreshTimers() { - app.refreshTimers.forEach((timer) => clearInterval(timer)); - app.refreshTimers = []; - } - - function swapPage(html, url, push = true) { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const targetPath = new URL(url, window.location.href).pathname; - if (requiresFullLoad(targetPath)) { - window.location.href = url; - return; - } - const newMain = qs('.v-main', doc) || qs('.shell', doc); - const currentMain = qs('.v-main') || qs('.shell'); - - if (!newMain || !currentMain) { - window.location.href = url; - return; - } - - stopRefreshTimers(); - document.title = doc.title || document.title; - currentMain.classList.add('page-exit'); - setTimeout(() => { - currentMain.replaceWith(newMain); - newMain.classList.add('page-enter'); - setTimeout(() => newMain.classList.remove('page-enter'), 220); - activeSidebar(targetPath); - if (push) history.pushState({ ajax: true }, '', url); - initDynamicPage(); - window.scrollTo({ top: 0, behavior: 'smooth' }); - }, 80); - } - - async function ajaxNavigate(url, push = true) { - const targetPath = new URL(url, window.location.href).pathname; - if (requiresFullLoad(targetPath)) { - window.location.href = url; - return; - } - if (app.loading) return; - setLoading(true); - try { - const response = await fetch(url, { - headers: { - 'X-Requested-With': 'XMLHttpRequest', - 'Accept': 'text/html,application/xhtml+xml', - }, - cache: 'no-store', - }); - if (!response.ok) throw new Error('HTTP ' + response.status); - const html = await response.text(); - swapPage(html, url, push); - } catch (error) { - window.location.href = url; - } finally { - setLoading(false); - } - } - - function wireAjaxLinks(root = document) { - qsa('a', root).forEach((link) => { - if (link.dataset.ajaxWired === '1') return; - link.dataset.ajaxWired = '1'; - link.addEventListener('click', (event) => { - if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; - if (!isAjaxSafeLink(link)) return; - event.preventDefault(); - ajaxNavigate(link.href); - }); - }); - } - - function wireAjaxForms(root = document) { - qsa('form', root).forEach((form) => { - if (form.dataset.ajaxWired === '1') return; - if (form.dataset.noAjax === '1') return; - const method = String(form.method || 'GET').toUpperCase(); - if (method !== 'POST') return; - if (form.enctype && form.enctype.includes('multipart')) return; - form.dataset.ajaxWired = '1'; - form.addEventListener('submit', async (event) => { - if (event.defaultPrevented) return; - const submitter = event.submitter; - if (submitter && submitter.classList.contains('danger') && !confirm('Выполнить действие?')) return; - event.preventDefault(); - setLoading(true); - try { - const response = await fetch(form.action, { - method: 'POST', - body: new FormData(form), - headers: { 'X-Requested-With': 'XMLHttpRequest' }, - redirect: 'follow', - }); - const finalUrl = response.url || window.location.href; - if (response.headers.get('content-type')?.includes('text/html')) { - const html = await response.text(); - swapPage(html, finalUrl, true); - } else { - await ajaxNavigate(window.location.href, false); - } - } catch (error) { - form.submit(); - } finally { - setLoading(false); - } - }); - }); - } - async function refreshFragments() { const fragments = qsa('[data-refresh-url]'); for (const fragment of fragments) { @@ -186,6 +37,8 @@ function startAutoRefresh(root = document) { const interval = Number(qs('[data-auto-refresh]', root)?.dataset.autoRefresh || 0); if (interval > 0) { + app.refreshTimers.forEach((timer) => clearInterval(timer)); + app.refreshTimers = []; const timer = setInterval(refreshFragments, interval * 1000); app.refreshTimers.push(timer); refreshFragments(); @@ -211,17 +64,14 @@ document.body.appendChild(btn); } - function initDynamicPage() { - wireAjaxLinks(); - wireAjaxForms(); + function initPanel() { wireNavGroups(); wireMobileMenu(); activeSidebar(); startAutoRefresh(); } - window.addEventListener('popstate', () => ajaxNavigate(window.location.href, false)); - document.addEventListener('DOMContentLoaded', initDynamicPage); + document.addEventListener('DOMContentLoaded', initPanel); })(); /* Virtuality live status + toast layer */ @@ -273,7 +123,7 @@ }); } function updateDetailHeader(vms) { - const title = qs('.brand'); + const title = qs('.v-title'); if (!title || !Array.isArray(vms)) return; const vm = vms.find((item) => item.name === title.textContent.trim()); if (!vm) return; @@ -283,7 +133,7 @@ badge.textContent = vm.state || 'unknown'; } async function refreshLiveStatus() { - if (document.hidden || (!qs('.v-main') && !qs('.shell'))) return; + if (document.hidden || !qs('.v-main')) return; try { const r = await fetch('/live/status', { cache: 'no-store', headers: { 'Accept': 'application/json' }}); if (!r.ok) return;