|
1 | 1 | (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; |
7 | 60 |
|
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'); |
14 | 62 | hamburger.className = 'nav-hamburger'; |
15 | 63 | hamburger.setAttribute('aria-label', 'Toggle navigation'); |
16 | 64 | hamburger.setAttribute('aria-expanded', 'false'); |
17 | | - hamburger.textContent = '\u2630'; // ☰ |
| 65 | + hamburger.textContent = '\u2630'; |
18 | 66 | appHeader.insertBefore(hamburger, headerNav); |
19 | 67 |
|
20 | 68 | function closeHamburgerNav() { |
|
25 | 73 |
|
26 | 74 | hamburger.addEventListener('click', function (e) { |
27 | 75 | e.stopPropagation(); |
28 | | - var isOpen = headerNav.classList.toggle('nav-open'); |
| 76 | + const isOpen = headerNav.classList.toggle('nav-open'); |
29 | 77 | hamburger.setAttribute('aria-expanded', String(isOpen)); |
30 | | - hamburger.textContent = isOpen ? '\u2715' : '\u2630'; // ✕ or ☰ |
| 78 | + hamburger.textContent = isOpen ? '\u2715' : '\u2630'; |
31 | 79 | }); |
32 | 80 |
|
33 | | - // Close when a nav anchor link is clicked |
34 | 81 | 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(); |
38 | 87 | }); |
39 | 88 |
|
| 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 | + }); |
40 | 95 | 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'); }); |
43 | 98 | } |
44 | 99 | }); |
45 | 100 | } |
46 | 101 |
|
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, ' ')); |
59 | 114 | } |
| 115 | + wireInteractions(); |
60 | 116 | }); |
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 () {}); |
103 | 117 | })(); |
0 commit comments