Skip to content

Commit 78ab268

Browse files
committed
Update nav.js
1 parent 508bdc3 commit 78ab268

1 file changed

Lines changed: 88 additions & 74 deletions

File tree

public/nav.js

Lines changed: 88 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,68 @@
11
(function () {
2-
const btn = document.getElementById('logoutBtn');
3-
if (btn) btn.addEventListener('click', async () => {
4-
await fetch('/api/logout', { method: 'POST' });
5-
window.location.href = '/login';
6-
});
2+
const PATH = window.location.pathname || '/';
3+
4+
function isActive(href) {
5+
return PATH === href;
6+
}
7+
8+
function navLink(href, label) {
9+
const cls = 'nav-link' + (isActive(href) ? ' active' : '');
10+
return `<a href="${href}" class="${cls}">${label}</a>`;
11+
}
12+
13+
function buildHeader(appName, isAdmin) {
14+
const systemMenu = isAdmin ? `
15+
<div class="nav-dropdown">
16+
<span class="nav-link nav-dropdown-toggle${PATH.startsWith('/config') || PATH.startsWith('/personality') || PATH.startsWith('/heartbeat') || PATH.startsWith('/agents') || PATH.startsWith('/pipelines') || PATH.startsWith('/autoagent') || PATH.startsWith('/users') ? ' has-active' : ''}">SYSTEM</span>
17+
<div class="nav-dropdown-menu">
18+
${navLink('/config', 'CONFIG')}
19+
${navLink('/personality', 'PERSONALITY')}
20+
${navLink('/heartbeat', 'HEARTBEAT')}
21+
${navLink('/agents', 'AGENTS')}
22+
${navLink('/pipelines', 'PIPELINES')}
23+
${navLink('/autoagent', 'AUTOAGENT')}
24+
${navLink('/users', 'USERS')}
25+
</div>
26+
</div>` : '';
27+
const editorLink = isAdmin ? navLink('/editor', 'EDITOR') : '';
28+
29+
return `
30+
<header class="app-header">
31+
<div class="header-left">
32+
<a href="/dashboard" class="logo">${appName}</a>
33+
</div>
34+
<nav class="header-nav">
35+
${navLink('/dashboard', 'DASHBOARD')}
36+
${navLink('/app', 'CHAT')}
37+
${navLink('/projects', 'PROJECTS')}
38+
${navLink('/skills', 'SKILLS')}
39+
${navLink('/rag', 'KNOWLEDGE')}
40+
${systemMenu}
41+
${editorLink}
42+
${navLink('/my-data', 'MY DATA')}
43+
${navLink('/profile', 'ACCOUNT')}
44+
<button type="button" id="logoutBtn" class="btn btn-small">LOGOUT</button>
45+
</nav>
46+
</header>
47+
`;
48+
}
49+
50+
function wireInteractions() {
51+
const btn = document.getElementById('logoutBtn');
52+
if (btn) btn.addEventListener('click', async () => {
53+
await fetch('/api/logout', { method: 'POST' });
54+
window.location.href = '/login';
55+
});
56+
57+
const appHeader = document.querySelector('.app-header');
58+
const headerNav = document.querySelector('.header-nav');
59+
if (!appHeader || !headerNav) return;
760

8-
// Inject hamburger button for mobile nav
9-
var appHeader = document.querySelector('.app-header');
10-
var headerNav = document.querySelector('.header-nav');
11-
var headerLeft = document.querySelector('.header-left');
12-
if (appHeader && headerNav && headerLeft) {
13-
var hamburger = document.createElement('button');
61+
const hamburger = document.createElement('button');
1462
hamburger.className = 'nav-hamburger';
1563
hamburger.setAttribute('aria-label', 'Toggle navigation');
1664
hamburger.setAttribute('aria-expanded', 'false');
17-
hamburger.textContent = '\u2630'; // ☰
65+
hamburger.textContent = '\u2630';
1866
appHeader.insertBefore(hamburger, headerNav);
1967

2068
function closeHamburgerNav() {
@@ -25,79 +73,45 @@
2573

2674
hamburger.addEventListener('click', function (e) {
2775
e.stopPropagation();
28-
var isOpen = headerNav.classList.toggle('nav-open');
76+
const isOpen = headerNav.classList.toggle('nav-open');
2977
hamburger.setAttribute('aria-expanded', String(isOpen));
30-
hamburger.textContent = isOpen ? '\u2715' : '\u2630'; // ✕ or ☰
78+
hamburger.textContent = isOpen ? '\u2715' : '\u2630';
3179
});
3280

33-
// Close when a nav anchor link is clicked
3481
headerNav.addEventListener('click', function (e) {
35-
if (e.target.closest('a.nav-link')) {
36-
closeHamburgerNav();
37-
}
82+
if (e.target.closest('a.nav-link')) closeHamburgerNav();
83+
});
84+
85+
document.addEventListener('click', function (e) {
86+
if (!e.target.closest('.app-header')) closeHamburgerNav();
3887
});
3988

89+
document.querySelectorAll('.nav-dropdown-toggle').forEach(function (toggle) {
90+
toggle.addEventListener('click', function (e) {
91+
e.stopPropagation();
92+
toggle.closest('.nav-dropdown').classList.toggle('open');
93+
});
94+
});
4095
document.addEventListener('click', function (e) {
41-
if (!e.target.closest('.app-header')) {
42-
closeHamburgerNav();
96+
if (!e.target.closest('.nav-dropdown')) {
97+
document.querySelectorAll('.nav-dropdown.open').forEach(function (d) { d.classList.remove('open'); });
4398
}
4499
});
45100
}
46101

47-
// Dropdown toggle (click for mobile / keyboard)
48-
document.querySelectorAll('.nav-dropdown-toggle').forEach(function (toggle) {
49-
toggle.addEventListener('click', function (e) {
50-
e.stopPropagation();
51-
toggle.closest('.nav-dropdown').classList.toggle('open');
52-
});
53-
});
54-
document.addEventListener('click', function (e) {
55-
if (!e.target.closest('.nav-dropdown')) {
56-
document.querySelectorAll('.nav-dropdown.open').forEach(function (d) {
57-
d.classList.remove('open');
58-
});
102+
Promise.all([
103+
fetch('/api/me').then(r => (r.ok ? r.json() : null)).catch(() => null),
104+
fetch('/api/app-name').then(r => (r.ok ? r.json() : null)).catch(() => null)
105+
]).then(([me, app]) => {
106+
const appName = (app && app.appName && String(app.appName).trim()) || 'SHADOW_AI';
107+
const isAdmin = !!(me && me.role === 'admin');
108+
const existing = document.querySelector('.app-header');
109+
if (existing) existing.outerHTML = buildHeader(appName, isAdmin);
110+
else document.body.insertAdjacentHTML('afterbegin', buildHeader(appName, isAdmin));
111+
112+
if (document.title && document.title.indexOf('ShadowAI') !== -1) {
113+
document.title = document.title.replace(/ShadowAI/g, appName.replace(/_/g, ' '));
59114
}
115+
wireInteractions();
60116
});
61-
62-
// Apply configured app name to header logo and page title
63-
fetch('/api/app-name')
64-
.then(function (r) { return r.ok ? r.json() : null; })
65-
.then(function (d) {
66-
if (!d || !d.appName) return;
67-
const name = String(d.appName).trim();
68-
if (!name) return;
69-
const logo = document.querySelector('.logo');
70-
if (logo) logo.textContent = name;
71-
if (document.title && document.title.indexOf('ShadowAI') !== -1) {
72-
document.title = document.title.replace(/ShadowAI/g, name.replace(/_/g, ' '));
73-
}
74-
})
75-
.catch(function () {});
76-
77-
// Hide admin-only nav items for non-admin users
78-
fetch('/api/me')
79-
.then(function (r) { return r.ok ? r.json() : null; })
80-
.then(function (d) {
81-
// Ensure My Data appears in menu for all authenticated users.
82-
var headerNav = document.querySelector('.header-nav');
83-
if (headerNav && !headerNav.querySelector('a.nav-link[href="/my-data"]')) {
84-
var accountLink = headerNav.querySelector('a.nav-link[href="/profile"]');
85-
var myData = document.createElement('a');
86-
myData.href = '/my-data';
87-
myData.className = 'nav-link';
88-
myData.textContent = 'MY DATA';
89-
if (window.location.pathname === '/my-data') myData.classList.add('active');
90-
if (accountLink && accountLink.parentNode) accountLink.parentNode.insertBefore(myData, accountLink);
91-
else headerNav.appendChild(myData);
92-
}
93-
if (d && d.role === 'admin') return; // admins see everything
94-
// Hide SYSTEM dropdown and EDITOR link
95-
document.querySelectorAll('.header-nav .nav-dropdown').forEach(function (el) {
96-
el.style.display = 'none';
97-
});
98-
document.querySelectorAll('.header-nav a.nav-link[href="/editor"]').forEach(function (el) {
99-
el.style.display = 'none';
100-
});
101-
})
102-
.catch(function () {});
103117
})();

0 commit comments

Comments
 (0)