From d766df783325ec1bcf9e447f598817ac8e2309a7 Mon Sep 17 00:00:00 2001 From: qinglong Date: Sun, 14 Jun 2026 15:07:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20app=5Fv3.js=20+=20=E5=BC=BA=E5=88=B6?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E6=8F=92=E4=BB=B6=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/web_panel/home.html | 2 +- static/web_panel/js/app_v3.js | 360 ++++++++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 static/web_panel/js/app_v3.js diff --git a/static/web_panel/home.html b/static/web_panel/home.html index 9a75c3d..d16751c 100644 --- a/static/web_panel/home.html +++ b/static/web_panel/home.html @@ -79,6 +79,6 @@ - + diff --git a/static/web_panel/js/app_v3.js b/static/web_panel/js/app_v3.js new file mode 100644 index 0000000..08594cd --- /dev/null +++ b/static/web_panel/js/app_v3.js @@ -0,0 +1,360 @@ +/* โ”€โ”€ SenSu WebUI โ€” Theme + Routing + Page Loader โ”€โ”€ */ + +// โ•โ•โ• ไธป้ข˜ๅˆๅง‹ๅŒ– (ๅฟ…้กปๅœจๆธฒๆŸ“ๅ‰, ้˜ฒ้—ช็™ฝ) โ•โ•โ• +(function(){ + var s = localStorage.getItem("sensu-theme") || "dark"; + document.documentElement.setAttribute("data-theme", s); +})(); + +// โ•โ•โ• ๅทฅๅ…ทๅ‡ฝๆ•ฐ โ•โ•โ• +function getCookie(name) { + var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); + return match ? match[2] : ''; +} + +// โ”€โ”€ ้กถ้ƒจ็‰ˆๆœฌๆฃ€ๆŸฅ โ”€โ”€ +async function checkTopUpdate() { + var btn = document.getElementById("top-ver-btn"); + try { + var r = await fetch("./api/updates"), d = await r.json(); + var ver = d.current_version || d.latest_version || ""; + btn.textContent = ver ? ver : "v?"; + if (d.update_available) { + btn.textContent = "โ†ป " + d.latest_version + " ๅฏ็”จ"; + btn.style.color = "var(--success)"; + btn.title = "ๆ–ฐ็‰ˆๆœฌๅฏ็”จ! " + d.latest_version; + } else if (ver) { + btn.style.color = ""; + btn.title = "ๅทฒๆ˜ฏๆœ€ๆ–ฐ " + ver; + } + } catch(e) {} +} +setTimeout(checkTopUpdate, 3000); +setInterval(checkTopUpdate, 600000); // 10min + +window.toggleTheme = function(){ + var t = document.documentElement.getAttribute("data-theme") === "light" ? "dark" : "light"; + document.documentElement.setAttribute("data-theme", t); + localStorage.setItem("sensu-theme", t); + var btn = document.querySelector(".theme-toggle"); + if(btn) btn.textContent = t === "light" ? "โ˜€๏ธ" : "๐ŸŒ™"; +}; + + +// โ•โ•โ• ่ทฏ็”ฑ็ณป็ปŸ โ•โ•โ• +// Hash format: #/dashboard | #/files | #/plugins/example_plugin +// Built-in pages map to ./static/pages/{name}.html +// Plugin pages load from /plugin/{name} + +var BUILTIN_PAGES = ['dashboard','logs','console','plugins','projects','proxy','files','settings']; +var _pluginPagesCache = null; // {plugin_name: {path, title, icon}} + +function currentRoute() { + var h = location.hash.replace('#', '') || '/dashboard'; + return h.replace(/^\/+/, ''); +} + +function navigateTo(route) { + if (!route) route = 'dashboard'; + location.hash = '#' + route; // triggers hashchange โ†’ routePage() +} + +function routePage() { + var r = currentRoute(); + if (r.startsWith('plugins/')) { + loadPluginPage(r.replace('plugins/', '')); + } else if (BUILTIN_PAGES.indexOf(r) >= 0) { + loadPage(r); + } else { + loadPage('dashboard'); + location.hash = '#/dashboard'; + } +} + +// โ•โ•โ• onload โ•โ•โ• +window.onload = async function() { + var btn = document.querySelector(".theme-toggle"); + if(btn) btn.textContent = document.documentElement.getAttribute("data-theme") === "light" ? "โ˜€๏ธ" : "๐ŸŒ™"; + + try { + var res = await fetch('./api/auth/status', {credentials:'include'}); + if(res.status === 401 || !res.ok) { + sessionStorage.setItem('sensu_redirect_hash', location.hash); + window.location.href = './index.html'; return; + } + var data = await res.json(); + if(!data.authenticated) { + sessionStorage.setItem('sensu_redirect_hash', location.hash); + window.location.href = './index.html'; return; + } + document.getElementById('uname').textContent = data.username || 'Admin'; + } catch(e) { window.location.href = './index.html'; } + + window.addEventListener('hashchange', routePage); + routePage(); // load from current hash (or default to dashboard) +}; + + +// โ•โ•โ• Page loader (built-in pages) โ•โ•โ• +async function loadPage(pageName) { + var content = document.getElementById('page-content'); + var bar = document.getElementById('progress'); + + // Sidebar highlight + document.querySelectorAll('.nav-item').forEach(function(el){ el.classList.remove('active'); }); + document.querySelectorAll('.nav-sub-item').forEach(function(el){ el.classList.remove('active'); }); + var navItem = document.querySelector('.nav-item[data-page="'+pageName+'"]'); + if(navItem) navItem.classList.add('active'); + content.style.padding = ''; + + // Progress + bar.classList.add('active'); bar.style.width = '0%'; + await new Promise(function(r){ requestAnimationFrame(function(){ bar.style.width='80%'; setTimeout(r,100); }); }); + + // Destroy previous module + var modName = pageName.charAt(0).toUpperCase() + pageName.slice(1) + 'Module'; + if(window[modName] && window[modName].destroy) { try { window[modName].destroy(); } catch(e){} } + + try { + var resp = await fetch('./static/pages/'+pageName+'.html?t='+Date.now()); + if(!resp.ok) throw new Error('404'); + var html = await resp.text(); + + var scripts = []; + var cleanHtml = html.replace(/]*>([\s\S]*?)<\/script>/gi, function(m,code){ scripts.push(code.trim()); return ''; }); + content.innerHTML = cleanHtml; + + scripts.forEach(function(code){ + try { var s=document.createElement('script'); s.textContent=code; document.head.appendChild(s); document.head.removeChild(s); } + catch(ex){ console.error('Inline script error:', ex); } + }); + + // External JS module + try { + var jsResp = await fetch('./static/pages/'+pageName+'.js?t='+Date.now()); + if(jsResp.ok) { + var jsCode = await jsResp.text(); + var s=document.createElement('script'); s.textContent=jsCode; document.head.appendChild(s); document.head.removeChild(s); + if(window[modName] && window[modName].init) window[modName].init(); + } + } catch(e){ console.error('Page JS error:', pageName, e); } + + bar.style.width = '100%'; + setTimeout(function(){ bar.classList.remove('active'); }, 200); + } catch(e) { + content.innerHTML = '
้กต้ขๅŠ ่ฝฝๅคฑ่ดฅ: '+e.message+'
'; + bar.style.background='var(--error)'; + setTimeout(function(){ bar.style.width='100%'; setTimeout(function(){ bar.classList.remove('active'); bar.style.background='var(--primary)'; },200); },100); + } +} + + +// โ•โ•โ• Plugin page loader โ•โ•โ• +async function loadPluginPage(pluginName) { + var content = document.getElementById('page-content'); + var bar = document.getElementById('progress'); + + // Highlight sub-item + document.querySelectorAll('.nav-item').forEach(function(el){ el.classList.remove('active'); }); + document.querySelectorAll('.nav-sub-item').forEach(function(el){ el.classList.remove('active'); }); + var sub = document.querySelector('.nav-sub-item[data-plugin="'+pluginName+'"]'); + if(sub) sub.classList.add('active'); + + bar.classList.add('active'); bar.style.width='0%'; + await new Promise(function(r){ requestAnimationFrame(function(){ bar.style.width='80%'; setTimeout(r,100); }); }); + + // Also load plugin pages list if not yet + if(!_pluginPagesCache) await fetchPluginPages(); + + try { + var resp = await fetch('/plugin/'+pluginName+'?t='+Date.now(), {credentials:'include'}); + if(!resp.ok) throw new Error(resp.status); + var html = await resp.text(); + + var styles = ''; + html.replace(/]*>([\s\S]*?)<\/style>/gi, function(m,code){ styles+=code; return ''; }); + + var bodyMatch = html.match(/]*>([\s\S]*)<\/body>/i); + var clean = bodyMatch ? bodyMatch[1] : html.replace(/]*>|<\/html>|]*>[\s\S]*?<\/head>|]*>|<\/body>/gi, ''); + + var scripts = []; + clean = clean.replace(/]*>([\s\S]*?)<\/script>/gi, function(m,code){ scripts.push(code.trim()); return ''; }); + + // Scope plugin styles: replace body selector with .plugin-page-root to avoid + // styling the main page's element + styles = styles.replace(/body\s*\{/gi, '.plugin-page-root{').replace(/body\b/gi, '.plugin-page-root'); + + content.style.padding = '0'; + content.innerHTML = '
'+clean+'
'; + + scripts.forEach(function(code){ + try { var s=document.createElement('script'); s.textContent=code; document.head.appendChild(s); document.head.removeChild(s); } + catch(ex){ console.error('Plugin script error:', ex); } + }); + + bar.style.width='100%'; + setTimeout(function(){ bar.classList.remove('active'); }, 200); + } catch(ex) { + content.innerHTML = '
ๆ’ไปถ้กต้ขๅŠ ่ฝฝๅคฑ่ดฅ: '+ex.message+'
'; + bar.style.background='var(--error)'; + setTimeout(function(){ bar.style.width='100%'; setTimeout(function(){ bar.classList.remove('active'); bar.style.background='var(--primary)'; },200); },100); + } +} + + +// โ•โ•โ• Global file/dir picker (in-page overlay, no iframe) โ•โ•โ• +window.pickPath = function(mode, callback) { + var id = 'picker_' + Date.now(); + + // Create modal overlay + var overlay = document.createElement('div'); + overlay.id = 'fm-picker-overlay'; + overlay.style.cssText = 'position:fixed;inset:0;z-index:500;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center'; + + var box = document.createElement('div'); + box.style.cssText = 'background:var(--md-sys-color-surface-container-high);border-radius:var(--shape-lg);width:720px;height:520px;max-width:95vw;max-height:85vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--md-sys-elevation-5)'; + + // Header with close button + var header = document.createElement('div'); + header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--outline);flex-shrink:0'; + var headerTitle = document.createElement('span'); + headerTitle.style.cssText = 'font-weight:500;color:var(--primary)'; + headerTitle.textContent = '๐Ÿ“‚ ้€‰ๆ‹ฉ' + (mode==='file'?'ๆ–‡ไปถ':'็›ฎๅฝ•'); + header.appendChild(headerTitle); + var closeBtn = document.createElement('span'); + closeBtn.style.cssText = 'cursor:pointer;font-size:20px;line-height:1;padding:4px 8px;border-radius:4px;color:var(--text-dim)'; + closeBtn.textContent = 'โœ•'; + closeBtn.onclick = function(){ closePicker(); }; + header.appendChild(closeBtn); + box.appendChild(header); + + // Content area โ€” load file manager via fetch + var contentArea = document.createElement('div'); + contentArea.style.cssText = 'flex:1;overflow:hidden;display:flex;flex-direction:column'; + box.appendChild(contentArea); + overlay.appendChild(box); + document.body.appendChild(overlay); + + // Close on backdrop click + overlay.addEventListener('click', function(e){ if(e.target===overlay) closePicker(); }); + + function closePicker() { + if(overlay.parentNode) overlay.parentNode.removeChild(overlay); + } + + // Load file manager HTML into content area + fetch('./static/pages/files.html?t=' + Date.now()) + .then(function(r){ return r.text(); }) + .then(function(html){ + // Strip meta and CSS link (already available in main page) + html = html.replace(/]*>/gi, '').replace(/]*>/gi, ''); + // Extract inline scripts + var scripts = []; + var clean = html.replace(/]*>([\s\S]*?)<\/script>/gi, function(m,code){ scripts.push(code.trim()); return ''; }); + contentArea.innerHTML = clean; + // Execute scripts + scripts.forEach(function(code){ + try { var s=document.createElement('script'); s.textContent=code; document.head.appendChild(s); document.head.removeChild(s); } + catch(ex){} + }); + // Load files.js if not already loaded + // Fix container height for overlay + var fmContainer = contentArea.querySelector('.fm-container'); + if(fmContainer) fmContainer.style.height = '100%'; + function doInit(){ + window.FilesModule._apiBase = ''; + window.FilesModule.currentPath = ''; + window.FilesModule.pickerMode = mode || 'dir'; + window.FilesModule.pickerCallback = id; + window.FilesModule._scope = contentArea; + window.FilesModule._getEl = function(sel){ return contentArea.querySelector(sel); }; + // Override _pickResult to close overlay instead of alert + window.FilesModule._pickResult = function(path){ + closePicker(); + if(callback) callback(path || window.FilesModule.currentPath || '/'); + }; + if(window.FilesModule.destroy) window.FilesModule.destroy(); + if(window.FilesModule.init) window.FilesModule.init(); + } + // Override picker actions to close overlay and call callback + window._fmPick = function(){ + var p = window.FilesModule && window.FilesModule.currentPath || '/'; + closePicker(); + if(callback) callback(p); + }; + window._fmCancel = function(){ closePicker(); }; + + if(window.FilesModule){ doInit(); } + else { + var s=document.createElement('script'); + s.src='./static/pages/files.js?v=0603'; + s.onload=doInit; + document.head.appendChild(s); + } + }) + .catch(function(e){ + contentArea.innerHTML = '
ๅŠ ่ฝฝๅคฑ่ดฅ: '+e.message+'
'; + }); +}; + +// โ•โ•โ• Sidebar toggle โ•โ•โ• +function toggleSidebar() { + document.getElementById('app').classList.toggle('collapsed'); +} + +// โ•โ•โ• Logout โ•โ•โ• +async function doLogout() { + await fetch('./api/logout', {method:'POST', credentials:'include'}); + window.location.href = './index.html'; +} + + +// โ•โ•โ• Plugin pages expand/collapse โ•โ•โ• +async function fetchPluginPages() { + try { + var res = await fetch('./api/plugin-pages', {credentials:'include'}); + var data = await res.json(); + _pluginPagesCache = {}; + (data.pages||[]).forEach(function(p){ _pluginPagesCache[p.plugin] = p; }); + return _pluginPagesCache; + } catch(e) { return {}; } +} + +async function togglePluginPages() { + var body = document.getElementById('nav-plugins-body'); + var arrow = document.getElementById('nav-plugins-arrow'); + var isOpen = body.style.display !== 'none'; + if(isOpen){ body.style.display='none'; arrow.style.transform=''; return; } + body.style.display='block'; + arrow.style.transform='rotate(180deg)'; + + if(!_pluginPagesCache) await fetchPluginPages(); + var pages = _pluginPagesCache || {}; + var names = Object.keys(pages); + if(names.length) { + var html = ''; + names.forEach(function(name){ + var p = pages[name]; + html += ''; + }); + body.innerHTML = html; + } +} + +// โ•โ•โ• Click delegation โ•โ•โ• +document.addEventListener('click', function(e) { + // Built-in nav + var nav = e.target.closest('.nav-item'); + if(nav && nav.dataset.page) { + navigateTo(nav.dataset.page); + e.preventDefault(); + return; + } + // Plugin sub-item + var sub = e.target.closest('.nav-sub-item'); + if(sub && sub.dataset.plugin) { + navigateTo('plugins/' + sub.dataset.plugin); + e.preventDefault(); + } +});