Files
Nexus/web/app/layout.js
T

78 lines
3.6 KiB
JavaScript
Raw Normal View History

// 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) {}
}