4a989bf50f
后端: - 新增 /api/system/ws WebSocket端点 替代HTTP轮询 (每2s推送系统+框架数据) - 修复 uptime始终为0 (sm.start_time时间戳) - 网络采集新增TX上行+实时网速(delta法) - 进程内存采集 (/proc/self/status VmRSS) - 抑制aiohttp内部WS帧日志防刷爆日志文件 前端: - 仪表盘: HTTP轮询→WS连接+指数退避自动重连 - 实时日志: 修复onclose重连bug+批量渲染30fps+500行上限防卡死 - 网速卡片: DualLineChart双线图(下行实线/上行虚线) - Y轴零点偏移-5% 防止零网速贴底 - 所有按钮修复MD3组合类(btn+btn-tonal+btn-sm) - 页面加载器: 改动态script元素执行 修复onclick全局作用域问题 - emoji图标→Material Design SVG图标 - CSS去残留</style>+新增.nav-item svg约束 - 登录页输入框+标签左对齐+按钮MD3样式 - 多个版本号显示修复(去双重v前缀) - chart.js/app.js/HTML页面统一加版本号防浏览器缓存 - .gitignore新增docs/目录 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
109 lines
4.3 KiB
JavaScript
109 lines
4.3 KiB
JavaScript
// 主题初始化 (必须在渲染前执行, 防止闪白)
|
|
(function(){
|
|
var s = localStorage.getItem("sensu-theme") || "dark";
|
|
document.documentElement.setAttribute("data-theme", s);
|
|
})();
|
|
|
|
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" ? "☀️" : "🌙";
|
|
};
|
|
|
|
// 初始化检查
|
|
window.onload = async () => {
|
|
// 设置按钮初始图标
|
|
var btn = document.querySelector(".theme-toggle");
|
|
if(btn) btn.textContent = document.documentElement.getAttribute("data-theme") === "light" ? "☀️" : "🌙";
|
|
try {
|
|
const res = await fetch('./api/auth/status', { credentials: 'include' });
|
|
if(res.status === 401) { window.location.href = './index.html'; return; }
|
|
const data = await res.json();
|
|
if(!data.authenticated) { window.location.href = './index.html'; return; }
|
|
|
|
document.getElementById('uname').textContent = data.username || 'Admin';
|
|
loadPage('dashboard'); // 默认加载
|
|
} catch(e) { window.location.href = './index.html'; }
|
|
};
|
|
|
|
// 路由加载器
|
|
|
|
async function loadPage(pageName) {
|
|
const content = document.getElementById('page-content');
|
|
const bar = document.getElementById('progress');
|
|
|
|
// 侧边栏高亮
|
|
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
|
|
document.querySelector(`.nav-item[data-page="${pageName}"]`)?.classList.add('active');
|
|
|
|
// 进度条动画
|
|
bar.classList.add('active'); bar.style.width = '0%';
|
|
await new Promise(r => requestAnimationFrame(() => { bar.style.width = '80%'; setTimeout(r, 100); }));
|
|
|
|
try {
|
|
const resp = await fetch(`./static/pages/${pageName}.html`);
|
|
if(!resp.ok) throw new Error('404');
|
|
const html = await resp.text();
|
|
|
|
// Extract inline scripts before innerHTML (browsers skip them)
|
|
const scripts = [];
|
|
const cleanHtml = html.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, (m, code) => {
|
|
scripts.push(code.trim()); return '';
|
|
});
|
|
|
|
content.innerHTML = cleanHtml;
|
|
|
|
// Execute inline scripts via dynamic <script> tag so function
|
|
// declarations become globally accessible (onclick handlers need them)
|
|
for(const code of scripts) {
|
|
try {
|
|
const s = document.createElement('script');
|
|
s.textContent = code;
|
|
document.head.appendChild(s);
|
|
document.head.removeChild(s);
|
|
} catch(e) { console.error('Inline script error:', e); }
|
|
}
|
|
|
|
// Load external JS module the same way (optional — skip if 404)
|
|
try {
|
|
const jsResp = await fetch(`./static/pages/${pageName}.js?t=${Date.now()}`);
|
|
if(jsResp.ok) {
|
|
const jsCode = await jsResp.text();
|
|
const s = document.createElement('script');
|
|
s.textContent = jsCode;
|
|
document.head.appendChild(s);
|
|
document.head.removeChild(s);
|
|
const moduleName = pageName.charAt(0).toUpperCase() + pageName.slice(1) + 'Module';
|
|
if(window[moduleName]?.init) window[moduleName].init();
|
|
}
|
|
} catch(e) { console.error('Page JS error:', pageName, e); }
|
|
|
|
bar.style.width = '100%';
|
|
setTimeout(() => bar.classList.remove('active'), 200);
|
|
} catch(e) {
|
|
content.innerHTML = `<div style="color:var(--error); text-align:center; margin-top:20vh;">页面加载失败: ${e.message}</div>`;
|
|
bar.style.background = 'var(--error)';
|
|
setTimeout(() => { bar.style.width = '100%'; setTimeout(() => { bar.classList.remove('active'); bar.style.background = 'var(--primary)'; }, 200); }, 100);
|
|
}
|
|
}
|
|
|
|
|
|
// 侧边栏切换
|
|
function toggleSidebar() {
|
|
document.getElementById('app').classList.toggle('collapsed');
|
|
}
|
|
|
|
// 退出登录
|
|
async function doLogout() {
|
|
await fetch('./api/logout', { method: 'POST', credentials: 'include' });
|
|
window.location.href = './index.html';
|
|
}
|
|
|
|
// 点击侧边栏事件委托
|
|
document.addEventListener('click', (e) => {
|
|
const nav = e.target.closest('.nav-item');
|
|
if(nav) { loadPage(nav.dataset.page); e.preventDefault(); }
|
|
});
|