f262876b25
- 新增 layout.js: initLayout() 自动生成侧边栏+高亮+用户信息 - retries/audit/schedules 已迁移到共享布局 - 减少约200行重复HTML代码
78 lines
3.6 KiB
JavaScript
78 lines
3.6 KiB
JavaScript
// Nexus — Shared Sidebar + Layout Component
|
|
// Usage: Include after Alpine.js. Call initLayout() on each page.
|
|
// Sets sidebar, highlights active nav item, loads user info.
|
|
|
|
function initLayout(activeNav) {
|
|
// activeNav: 'index'|'servers'|'files'|'push'|'scripts'|'credentials'|'schedules'|'retries'|'audit'|'settings'
|
|
const navItems = [
|
|
{ id:'index', icon:'🏠', label:'仪表盘', href:'/app/index.html' },
|
|
{ id:'servers', icon:'🖥', label:'服务器', href:'/app/servers.html' },
|
|
{ id:'files', icon:'📁', label:'文件管理', href:'/app/files.html' },
|
|
{ id:'push', icon:'📤', label:'推送', href:'/app/push.html' },
|
|
{ id:'scripts', icon:'📜', label:'脚本库', href:'/app/scripts.html' },
|
|
{ id:'credentials', icon:'🔑', label:'凭据', href:'/app/credentials.html' },
|
|
{ id:'schedules', icon:'⏰', label:'调度', href:'/app/schedules.html' },
|
|
{ id:'retries', icon:'🔄', label:'重试队列', href:'/app/retries.html' },
|
|
];
|
|
|
|
const bottomNav = [
|
|
{ id:'audit', icon:'📋', label:'审计日志', href:'/app/audit.html' },
|
|
{ id:'settings', icon:'⚙️', label:'设置', href:'/app/settings.html' },
|
|
];
|
|
|
|
// Build sidebar HTML
|
|
const navHtml = navItems.map(n =>
|
|
`<a href="${n.href}" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition ${n.id===activeNav?'bg-brand/10 text-brand-light font-medium':'text-slate-400 hover:text-white hover:bg-slate-800'}">${n.icon} ${n.label}</a>`
|
|
).join('');
|
|
|
|
const bottomHtml = bottomNav.map(n =>
|
|
`<a href="${n.href}" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition ${n.id===activeNav?'bg-brand/10 text-brand-light font-medium':'text-slate-400 hover:text-white hover:bg-slate-800'}">${n.icon} ${n.label}</a>`
|
|
).join('');
|
|
|
|
// Find sidebar container
|
|
const sidebar = document.querySelector('[data-sidebar]');
|
|
if (!sidebar) return;
|
|
|
|
sidebar.innerHTML = `
|
|
<div class="px-5 py-4 border-b border-slate-800">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-8 h-8 rounded-lg bg-brand/20 flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-brand-light" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"/>
|
|
</svg>
|
|
</div>
|
|
<span id="brandName" class="text-white font-bold text-lg">Nexus</span>
|
|
</div>
|
|
</div>
|
|
<nav class="flex-1 overflow-y-auto py-3 px-3 space-y-1">
|
|
${navHtml}
|
|
<div class="border-t border-slate-800 my-2"></div>
|
|
${bottomHtml}
|
|
</nav>
|
|
<div class="border-t border-slate-800 p-4">
|
|
<div class="flex items-center justify-between">
|
|
<span id="sidebarUser" class="text-slate-400 text-sm">...</span>
|
|
<button onclick="doLogout()" class="text-slate-500 hover:text-red-400 text-xs transition">退出</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Load user info
|
|
loadLayoutUser();
|
|
}
|
|
|
|
async function loadLayoutUser() {
|
|
try {
|
|
const r = await apiFetch(API + '/auth/me');
|
|
if (!r) { doLogout(); return; }
|
|
const u = await r.json();
|
|
const el = document.getElementById('sidebarUser');
|
|
if (el) el.textContent = u.username;
|
|
const headerEl = document.getElementById('headerUser');
|
|
if (headerEl) headerEl.textContent = u.username;
|
|
// Set brand name
|
|
const brandEl = document.getElementById('brandName');
|
|
if (brandEl && u.system_name) brandEl.textContent = u.system_name;
|
|
} catch(e) {}
|
|
}
|