This commit is contained in:
KingKDot
2026-04-05 21:32:18 -05:00
parent 2470e9d1f2
commit 8b43934b83
7 changed files with 660 additions and 144 deletions
+353
View File
@@ -1992,3 +1992,356 @@ tbody tr:hover {
transform: none !important;
}
}
/* ============================================================
SIDEBAR LAYOUT
============================================================ */
:root {
--sb-w: 224px;
--sb-w-collapsed: 64px;
--sb-speed: 240ms;
--sb-ease: cubic-bezier(0.4, 0, 0.2, 1);
}
/* Offset body content so it sits beside the sidebar */
body.sb-ready {
padding-left: var(--sb-w);
transition: padding-left var(--sb-speed) var(--sb-ease);
}
body.sb-ready.sb-collapsed {
padding-left: var(--sb-w-collapsed);
}
/* On mobile the sidebar becomes a drawer, not a persistent rail */
@media (max-width: 767px) {
body.sb-ready,
body.sb-ready.sb-collapsed {
padding-left: 0;
padding-top: 52px;
}
}
/* ---- The sidebar element (#top-nav) ---- */
body.sb-ready #top-nav {
position: fixed;
top: 0;
left: 0;
height: 100dvh;
width: var(--sb-w);
background: rgba(2, 8, 22, 0.97);
border-right: 1px solid rgba(255, 255, 255, 0.07);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
display: flex;
flex-direction: column;
z-index: 50;
overflow: hidden;
padding: 0;
transition: width var(--sb-speed) var(--sb-ease);
}
body.sb-collapsed #top-nav {
width: var(--sb-w-collapsed);
}
@media (max-width: 767px) {
body.sb-ready #top-nav {
width: var(--sb-w) !important;
transform: translateX(calc(-100% - 2px));
box-shadow: 4px 0 32px rgba(0, 0, 0, 0.55);
z-index: 51;
transition: transform var(--sb-speed) var(--sb-ease);
}
body.sb-open #top-nav {
transform: translateX(0);
}
}
/* ---- Sidebar header (logo row) ---- */
.sb-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 16px 12px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
flex-shrink: 0;
min-width: 0;
}
body.sb-collapsed .sb-header {
justify-content: center;
padding: 16px 0 12px;
}
.sb-logo {
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
font-size: 0.95rem;
color: #f1f5f9;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
flex: 1;
min-width: 0;
opacity: 1;
transition: opacity var(--sb-speed) var(--sb-ease);
}
body.sb-collapsed .sb-logo {
opacity: 0;
pointer-events: none;
width: 0;
flex: 0;
overflow: hidden;
}
.sb-logo .sb-text {
overflow: hidden;
opacity: 1;
max-width: 160px;
transition: opacity var(--sb-speed) var(--sb-ease), max-width var(--sb-speed) var(--sb-ease);
}
body.sb-collapsed .sb-logo .sb-text {
max-width: 0;
opacity: 0;
pointer-events: none;
}
/* ---- Collapse toggle button (desktop only) ---- */
.sb-collapse-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
flex-shrink: 0;
border-radius: 7px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
color: #64748b;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.sb-collapse-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: #cbd5e1;
}
.sb-collapse-btn i {
display: block;
font-size: 0.7rem;
transition: transform var(--sb-speed) var(--sb-ease);
}
body.sb-collapsed .sb-collapse-btn i {
transform: rotate(180deg);
}
@media (max-width: 767px) {
.sb-collapse-btn { display: none; }
}
/* ---- Nav section ---- */
.sb-nav {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 8px;
display: flex;
flex-direction: column;
gap: 3px;
scrollbar-width: thin;
scrollbar-color: rgba(100, 116, 139, 0.25) transparent;
}
.sb-nav::-webkit-scrollbar { width: 4px; }
.sb-nav::-webkit-scrollbar-track { background: transparent; }
.sb-nav::-webkit-scrollbar-thumb {
background: rgba(100, 116, 139, 0.3);
border-radius: 4px;
}
/* ---- Shared sidebar link style (<a> and <button>) ---- */
.sb-link {
display: flex;
align-items: center;
gap: 11px;
padding: 10px 12px;
border-radius: 9px;
color: #7c8fa3;
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
border: 1px solid transparent;
background: transparent;
cursor: pointer;
width: 100%;
text-align: left;
position: relative;
transition: background 0.14s, color 0.14s, border-color 0.14s;
}
.sb-link:hover {
background: rgba(255, 255, 255, 0.06);
color: #e2e8f0;
}
/* Collapsed: center icon, fill the rail */
body.sb-collapsed .sb-link {
justify-content: center;
padding: 12px 0;
}
.sb-icon {
flex-shrink: 0;
width: 20px;
text-align: center;
font-size: 1rem;
}
body.sb-collapsed .sb-icon {
width: 22px;
font-size: 1.1rem;
}
.sb-link .sb-text {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
overflow: hidden;
white-space: nowrap;
opacity: 1;
max-width: 200px;
transition: opacity var(--sb-speed) var(--sb-ease), max-width var(--sb-speed) var(--sb-ease);
}
body.sb-collapsed .sb-link .sb-text {
max-width: 0;
opacity: 0;
pointer-events: none;
}
/* Active link */
.sb-link.nav-active {
background: rgba(99, 102, 241, 0.14);
color: #e2e8f0;
border-color: rgba(99, 102, 241, 0.28);
}
.sb-link.nav-active:hover {
background: rgba(99, 102, 241, 0.2);
}
/* Danger link (logout) */
.sb-link--danger { color: #f87171; }
.sb-link--danger:hover {
background: rgba(239, 68, 68, 0.1);
color: #fca5a5;
border-color: rgba(239, 68, 68, 0.28);
}
/* ---- Notification bell badge ---- */
.sb-notify-wrap {
position: relative;
flex-shrink: 0;
width: 18px;
display: flex;
align-items: center;
justify-content: center;
}
.sb-notify-badge {
position: absolute;
top: -5px;
right: -7px;
min-width: 15px;
height: 15px;
padding: 0 3px;
border-radius: 999px;
background: #e11d48;
color: #fff;
font-size: 0.6rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
/* ---- Inline badge (purgatory count) ---- */
.sb-inline-badge {
display: none;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 4px;
border-radius: 999px;
background: #d97706;
color: #fff;
font-size: 0.65rem;
font-weight: 700;
line-height: 1;
}
.sb-inline-badge:not(.hidden) {
display: inline-flex;
}
/* ---- Utility section (bottom of sidebar) ---- */
.sb-utility {
padding: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
/* ---- Mobile topbar (drawer handle) ---- */
#sb-mobile-bar {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
height: 52px;
z-index: 49;
background: rgba(2, 8, 22, 0.97);
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
align-items: center;
padding: 0 14px;
gap: 10px;
}
@media (max-width: 767px) {
#sb-mobile-bar { display: flex; }
}
.sb-mobile-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #94a3b8;
cursor: pointer;
font-size: 1rem;
}
.sb-mobile-brand {
font-weight: 600;
font-size: 0.95rem;
color: #f1f5f9;
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
/* ---- Mobile backdrop ---- */
#sb-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
z-index: 50;
backdrop-filter: blur(1px);
-webkit-backdrop-filter: blur(1px);
}
body.sb-open #sb-backdrop { display: block; }
/* Notify-toggle enabled/disabled color — ID specificity beats .sb-link */
#notify-toggle.text-emerald-200 { color: #a7f3d0; }
#notify-toggle.text-slate-300 { color: #cbd5e1; }
#notify-toggle.border-emerald-500\/40 { border-color: rgba(16, 185, 129, 0.4); }
+3 -12
View File
@@ -382,20 +382,11 @@ function initializeRenderer() {
anime
.timeline({ easing: "easeOutQuad" })
.add({
targets: "header",
targets: "main > div > div:first-child",
opacity: [0, 1],
translateY: [-20, 0],
duration: 600,
translateY: [15, 0],
duration: 500,
})
.add(
{
targets: "main > div > div:first-child",
opacity: [0, 1],
translateY: [15, 0],
duration: 500,
},
"-=400",
)
.add(
{
targets: "main > div > div:nth-child(2)",
+2 -6
View File
@@ -14,7 +14,7 @@ import { showCertBannerIfNeeded } from "./cert-banner.js";
const host = document.getElementById("top-nav");
if (host) {
const refs = mountNav(host);
showCertBannerIfNeeded(host);
showCertBannerIfNeeded(document.getElementById("sb-mobile-bar") || host);
const { applyAdaptiveNavLayout } = createAdaptiveNavController(host, refs);
const path = window.location.pathname;
@@ -34,11 +34,7 @@ if (host) {
const activeId = activeMap[path];
if (activeId) {
const el = document.getElementById(activeId);
if (el) {
el.classList.remove("bg-slate-900/70", "text-slate-300");
el.classList.add("bg-slate-800", "text-slate-50");
el.classList.add("nav-active");
}
if (el) el.classList.add("nav-active");
}
if (refs.logoutBtn && !refs.logoutBtn.dataset.boundLogout) {
refs.logoutBtn.dataset.boundLogout = "true";
+53 -21
View File
@@ -1,6 +1,9 @@
const MOBILE_BREAKPOINT = 768;
import { NAV_MODE_KEY } from "./template.js";
export function createAdaptiveNavController(host, refs) {
const LS_KEY = "sb_collapsed";
const MOBILE_BP = 768;
function createTopbarController(host, refs) {
const { toggle, panel, navLinks, navUtility } = refs;
if (!toggle || !panel || !navLinks || !navUtility) {
return { applyAdaptiveNavLayout: () => {} };
@@ -25,7 +28,7 @@ export function createAdaptiveNavController(host, refs) {
}
function applyAdaptiveNavLayout() {
if (window.innerWidth < MOBILE_BREAKPOINT) {
if (window.innerWidth < MOBILE_BP) {
host.dataset.navMode = "mobile";
panel.classList.add("hidden");
resetInlineStyles();
@@ -62,26 +65,21 @@ export function createAdaptiveNavController(host, refs) {
panel.style.flexDirection = "column";
panel.style.alignItems = "stretch";
panel.style.gap = "10px";
navLinks.style.flexDirection = "row";
navLinks.style.flexWrap = "wrap";
navLinks.style.alignItems = "center";
navLinks.style.justifyContent = "flex-start";
navUtility.style.display = "flex";
navUtility.style.width = "100%";
navUtility.style.justifyContent = "space-between";
navUtility.style.flexWrap = "wrap";
toggle.setAttribute("aria-expanded", "true");
}
function closeCompactPanel() {
panel.dataset.open = "false";
panel.style.display = "none";
if (host.dataset.navMode === "mobile") {
panel.classList.add("hidden");
}
if (host.dataset.navMode === "mobile") panel.classList.add("hidden");
toggle.setAttribute("aria-expanded", "false");
}
@@ -89,26 +87,60 @@ export function createAdaptiveNavController(host, refs) {
const compact =
host.dataset.navMode === "compact" || host.dataset.navMode === "mobile";
if (!compact) return;
const isOpen = panel.dataset.open === "true";
if (isOpen) {
if (panel.dataset.open === "true") {
closeCompactPanel();
return;
} else {
openCompactPanel();
}
openCompactPanel();
});
let resizeRaf = null;
window.addEventListener("resize", () => {
if (resizeRaf) {
cancelAnimationFrame(resizeRaf);
}
resizeRaf = requestAnimationFrame(() => {
applyAdaptiveNavLayout();
});
if (resizeRaf) cancelAnimationFrame(resizeRaf);
resizeRaf = requestAnimationFrame(applyAdaptiveNavLayout);
});
applyAdaptiveNavLayout();
return { applyAdaptiveNavLayout };
}
function createSidebarController(host, refs) {
const { collapseBtn, toggle } = refs;
const backdrop = document.getElementById("sb-backdrop");
const navLinks = document.getElementById("nav-links");
document.body.classList.add("sb-ready");
let collapsed = localStorage.getItem(LS_KEY) === "true";
if (collapsed) document.body.classList.add("sb-collapsed");
function setCollapsed(val) {
collapsed = val;
localStorage.setItem(LS_KEY, String(val));
document.body.classList.toggle("sb-collapsed", val);
}
function isMobile() { return window.innerWidth < MOBILE_BP; }
function openMobile() { document.body.classList.add("sb-open"); }
function closeMobile() { document.body.classList.remove("sb-open"); }
if (collapseBtn) {
collapseBtn.addEventListener("click", () => {
if (!isMobile()) setCollapsed(!collapsed);
});
}
if (toggle) toggle.addEventListener("click", openMobile);
if (backdrop) backdrop.addEventListener("click", closeMobile);
window.addEventListener("resize", () => { if (!isMobile()) closeMobile(); });
return { applyAdaptiveNavLayout: () => {} };
}
export function createAdaptiveNavController(host, refs) {
const mode = localStorage.getItem(NAV_MODE_KEY);
if (mode === "sidebar") {
return createSidebarController(host, refs);
}
return createTopbarController(host, refs);
}
+199 -105
View File
@@ -1,4 +1,6 @@
export function mountNav(host) {
export const NAV_MODE_KEY = "sb_mode";
function mountTopbar(host) {
host.className =
"sticky top-0 z-10 w-full px-5 py-3 bg-slate-950/80 backdrop-blur border-b border-slate-800";
@@ -22,110 +24,61 @@ export function mountNav(host) {
id="nav-panel"
class="hidden md:flex md:flex-1 md:items-center md:justify-between gap-3"
>
<nav id="nav-links" class="flex flex-wrap items-center gap-2 md:flex-1 md:justify-center">
<a
href="/"
id="nav-clients"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-display text-sky-400"></i> Clients</a
>
<a
href="/metrics"
id="metrics-link"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-chart-line text-emerald-400"></i> Metrics</a
>
<a
href="/logs"
id="logs-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-clipboard-list text-amber-400"></i> Logs</a
>
<a
href="/scripts"
id="scripts-link"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-code text-cyan-400"></i> Scripts</a
>
<a
href="/socks5-manager"
id="socks5-link"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-network-wired text-sky-400"></i> Proxies</a
>
<a
href="/plugins"
id="plugins-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-puzzle-piece text-violet-400"></i> Plugins</a
>
<a
href="/build"
id="build-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-hammer text-orange-400"></i> Builder</a
>
<a
href="/sol-publish"
id="sol-publish-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-link-slash text-purple-400"></i> Sol Publish</a
>
<a
href="/notifications"
id="notifications-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-bell text-yellow-400"></i> Notifications</a
>
<a
href="/users"
id="users-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-users text-indigo-400"></i> Users</a
>
<a
href="/purgatory"
id="enrollment-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-user-clock text-amber-400"></i> Purgatory<span id="enrollment-badge" class="hidden min-w-[20px] h-5 px-1 rounded-full bg-amber-500 text-white text-xs flex items-center justify-center"></span></a
>
</nav>
<div id="nav-utility" class="flex flex-wrap items-center gap-2 md:w-auto md:justify-end md:shrink-0">
<button
id="notify-toggle"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 text-slate-300 hover:bg-slate-800"
title="Toggle notifications"
aria-label="Toggle notifications"
>
<i class="fa-solid fa-bell"></i>
<span
id="notify-badge"
class="hidden min-w-[20px] h-5 px-1 rounded-full bg-rose-500 text-white text-xs flex items-center justify-center"
></span>
</button>
<button
id="account-settings-btn"
class="inline-flex items-center gap-2 px-3 py-2 rounded-full bg-slate-800 text-slate-100 min-w-0 max-w-full md:max-w-none border border-slate-700/70 hover:bg-slate-700 transition-colors"
title="Open settings"
aria-label="Open settings"
type="button"
>
<i class="fa-solid fa-user-shield text-sky-300"></i>
<span id="username-display" class="truncate max-w-[110px] sm:max-w-[180px] md:max-w-none">Loading...</span>
<span
id="role-badge"
class="text-sm px-2 py-0.5 rounded-full bg-slate-700 shrink-0"
></span>
</button>
<button
id="logout-btn"
class="group inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-red-900/40 hover:bg-red-800/60 text-red-100 border border-red-700/60 transition-colors"
title="Logout"
aria-label="Logout"
>
<i class="fa-solid fa-right-from-bracket text-rose-300 group-hover:text-rose-200 transition-colors"></i>
</button>
</div>
<nav id="nav-links" class="flex flex-wrap items-center gap-2 md:flex-1 md:justify-center">
<a href="/" id="nav-clients"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-display text-sky-400"></i> Clients</a>
<a href="/metrics" id="metrics-link"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-chart-line text-emerald-400"></i> Metrics</a>
<a href="/logs" id="logs-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-clipboard-list text-amber-400"></i> Logs</a>
<a href="/scripts" id="scripts-link"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-code text-cyan-400"></i> Scripts</a>
<a href="/socks5-manager" id="socks5-link"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-network-wired text-sky-400"></i> Proxies</a>
<a href="/plugins" id="plugins-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-puzzle-piece text-violet-400"></i> Plugins</a>
<a href="/build" id="build-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-hammer text-orange-400"></i> Builder</a>
<a href="/sol-publish" id="sol-publish-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-link-slash text-purple-400"></i> Sol Publish</a>
<a href="/notifications" id="notifications-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-bell text-yellow-400"></i> Notifications</a>
<a href="/users" id="users-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-users text-indigo-400"></i> Users</a>
<a href="/purgatory" id="enrollment-link"
class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors"
><i class="fa-solid fa-user-clock text-amber-400"></i> Purgatory<span id="enrollment-badge" class="hidden min-w-[20px] h-5 px-1 rounded-full bg-amber-500 text-white text-xs flex items-center justify-center"></span></a>
</nav>
<div id="nav-utility" class="flex flex-wrap items-center gap-2 md:w-auto md:justify-end md:shrink-0">
<button id="notify-toggle"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 text-slate-300 hover:bg-slate-800"
title="Toggle notifications" aria-label="Toggle notifications">
<i class="fa-solid fa-bell"></i>
<span id="notify-badge" class="hidden min-w-[20px] h-5 px-1 rounded-full bg-rose-500 text-white text-xs flex items-center justify-center"></span>
</button>
<button id="account-settings-btn"
class="inline-flex items-center gap-2 px-3 py-2 rounded-full bg-slate-800 text-slate-100 min-w-0 max-w-full md:max-w-none border border-slate-700/70 hover:bg-slate-700 transition-colors"
title="Open settings" aria-label="Open settings" type="button">
<i class="fa-solid fa-user-shield text-sky-300"></i>
<span id="username-display" class="truncate max-w-[110px] sm:max-w-[180px] md:max-w-none">Loading...</span>
<span id="role-badge" class="text-sm px-2 py-0.5 rounded-full bg-slate-700 shrink-0"></span>
</button>
<button id="logout-btn"
class="group inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-red-900/40 hover:bg-red-800/60 text-red-100 border border-red-700/60 transition-colors"
title="Logout" aria-label="Logout">
<i class="fa-solid fa-right-from-bracket text-rose-300 group-hover:text-rose-200 transition-colors"></i>
</button>
</div>
</div>
</div>
`;
@@ -133,6 +86,7 @@ export function mountNav(host) {
return {
toggle: document.getElementById("nav-toggle"),
panel: document.getElementById("nav-panel"),
collapseBtn: null,
navLinks: document.getElementById("nav-links"),
navUtility: document.getElementById("nav-utility"),
logoutBtn: document.getElementById("logout-btn"),
@@ -152,3 +106,143 @@ export function mountNav(host) {
enrollmentBadge: document.getElementById("enrollment-badge"),
};
}
function mountSidebar(host) {
// Sidebar HTML injected into the fixed #top-nav element
host.innerHTML = `
<div class="sb-header">
<a href="/" class="sb-logo">
<i class="fa-solid fa-crown header-crown sb-icon"></i>
<span class="sb-text">Overlord</span>
</a>
<button id="sb-collapse-btn" class="sb-collapse-btn" title="Toggle sidebar" aria-label="Toggle sidebar">
<i class="fa-solid fa-chevron-left"></i>
</button>
</div>
<nav id="nav-links" class="sb-nav">
<a href="/" id="nav-clients" class="sb-link" title="Clients">
<i class="fa-solid fa-display text-sky-400 sb-icon"></i>
<span class="sb-text">Clients</span>
</a>
<a href="/metrics" id="metrics-link" class="sb-link" title="Metrics">
<i class="fa-solid fa-chart-line text-emerald-400 sb-icon"></i>
<span class="sb-text">Metrics</span>
</a>
<a href="/logs" id="logs-link" class="sb-link hidden" title="Logs">
<i class="fa-solid fa-clipboard-list text-amber-400 sb-icon"></i>
<span class="sb-text">Logs</span>
</a>
<a href="/scripts" id="scripts-link" class="sb-link" title="Scripts">
<i class="fa-solid fa-code text-cyan-400 sb-icon"></i>
<span class="sb-text">Scripts</span>
</a>
<a href="/socks5-manager" id="socks5-link" class="sb-link" title="Proxies">
<i class="fa-solid fa-network-wired text-sky-400 sb-icon"></i>
<span class="sb-text">Proxies</span>
</a>
<a href="/plugins" id="plugins-link" class="sb-link hidden" title="Plugins">
<i class="fa-solid fa-puzzle-piece text-violet-400 sb-icon"></i>
<span class="sb-text">Plugins</span>
</a>
<a href="/build" id="build-link" class="sb-link hidden" title="Builder">
<i class="fa-solid fa-hammer text-orange-400 sb-icon"></i>
<span class="sb-text">Builder</span>
</a>
<a href="/sol-publish" id="sol-publish-link" class="sb-link hidden" title="Sol Publish">
<i class="fa-solid fa-link-slash text-purple-400 sb-icon"></i>
<span class="sb-text">Sol Publish</span>
</a>
<a href="/notifications" id="notifications-link" class="sb-link hidden" title="Notifications">
<i class="fa-solid fa-bell text-yellow-400 sb-icon"></i>
<span class="sb-text">Notifications</span>
</a>
<a href="/users" id="users-link" class="sb-link hidden" title="Users">
<i class="fa-solid fa-users text-indigo-400 sb-icon"></i>
<span class="sb-text">Users</span>
</a>
<a href="/purgatory" id="enrollment-link" class="sb-link hidden" title="Purgatory">
<i class="fa-solid fa-user-clock text-amber-400 sb-icon"></i>
<span class="sb-text">
Purgatory
<span id="enrollment-badge" class="sb-inline-badge hidden"></span>
</span>
</a>
</nav>
<div id="nav-utility" class="sb-utility">
<button id="notify-toggle" class="sb-link"
title="Toggle notifications" aria-label="Toggle notifications">
<span class="sb-notify-wrap sb-icon">
<i class="fa-solid fa-bell"></i>
<span id="notify-badge" class="sb-notify-badge hidden"></span>
</span>
<span class="sb-text">Notifications</span>
</button>
<button id="account-settings-btn" class="sb-link"
title="Open settings" aria-label="Open settings" type="button">
<i class="fa-solid fa-user-shield text-sky-300 sb-icon"></i>
<span class="sb-text">
<span id="username-display" class="truncate">Loading...</span>
<span id="role-badge" class="text-xs px-2 py-0.5 rounded-full bg-slate-700 shrink-0"></span>
</span>
</button>
<button id="logout-btn" class="sb-link sb-link--danger"
title="Logout" aria-label="Logout">
<i class="fa-solid fa-right-from-bracket sb-icon"></i>
<span class="sb-text">Logout</span>
</button>
</div>
`;
// Mobile topbar
const mobileBar = document.createElement("div");
mobileBar.id = "sb-mobile-bar";
mobileBar.innerHTML = `
<button id="nav-toggle" class="sb-mobile-toggle" aria-label="Open menu">
<i class="fa-solid fa-bars"></i>
</button>
<a href="/" class="sb-mobile-brand">
<i class="fa-solid fa-crown header-crown" style="font-size:0.85rem"></i>
<span>Overlord</span>
</a>
`;
host.insertAdjacentElement("afterend", mobileBar);
// Mobile backdrop
const backdrop = document.createElement("div");
backdrop.id = "sb-backdrop";
document.body.appendChild(backdrop);
return {
toggle: document.getElementById("nav-toggle"),
collapseBtn: document.getElementById("sb-collapse-btn"),
panel: host,
navLinks: document.getElementById("nav-links"),
navUtility: document.getElementById("nav-utility"),
logoutBtn: document.getElementById("logout-btn"),
notifyToggle: document.getElementById("notify-toggle"),
notifyBadge: document.getElementById("notify-badge"),
accountSettingsBtn: document.getElementById("account-settings-btn"),
usernameDisplay: document.getElementById("username-display"),
roleBadge: document.getElementById("role-badge"),
usersLink: document.getElementById("users-link"),
buildLink: document.getElementById("build-link"),
solPublishLink: document.getElementById("sol-publish-link"),
pluginsLink: document.getElementById("plugins-link"),
scriptsLink: document.getElementById("scripts-link"),
logsLink: document.getElementById("logs-link"),
notificationsLink: document.getElementById("notifications-link"),
enrollmentLink: document.getElementById("enrollment-link"),
enrollmentBadge: document.getElementById("enrollment-badge"),
};
}
export function mountNav(host) {
const mode = localStorage.getItem(NAV_MODE_KEY);
if (mode === "sidebar") {
return mountSidebar(host);
}
return mountTopbar(host);
}
+36
View File
@@ -7,6 +7,7 @@ import {
} from "./notify-client.js";
const PREF_REFRESH_KEY = "overlord_refresh_interval_seconds";
const NAV_MODE_KEY = "sb_mode";
const usernameEl = document.getElementById("settings-username");
const roleEl = document.getElementById("settings-role");
@@ -276,6 +277,17 @@ async function loadTlsSettings() {
setTlsFormDisabled(false);
}
function updateNavLayoutButtons(mode, sidebarBtn, topbarBtn) {
const active = ["bg-indigo-600/80", "border-indigo-500", "text-white"];
const inactive = ["bg-slate-800", "border-slate-700", "text-slate-400", "hover:bg-slate-700", "hover:text-slate-200"];
const base = ["nav-layout-btn", "flex-1", "flex", "items-center", "justify-center", "gap-2", "px-3", "py-2", "rounded-lg", "border", "text-sm", "font-medium", "transition-colors"];
sidebarBtn.className = [...base, ...(mode === "sidebar" ? active : inactive)].join(" ");
topbarBtn.className = [...base, ...(mode === "topbar" ? active : inactive)].join(" ");
sidebarBtn.dataset.selected = mode === "sidebar" ? "true" : "false";
topbarBtn.dataset.selected = mode === "topbar" ? "true" : "false";
}
function loadPrefs() {
prefNotificationsInput.checked = getNotificationsEnabled();
if (prefDesktopNotificationsInput) {
@@ -283,6 +295,13 @@ function loadPrefs() {
}
const refreshSeconds = Number(localStorage.getItem(PREF_REFRESH_KEY) || 8);
prefRefreshSecondsInput.value = String(Math.min(120, Math.max(3, refreshSeconds)));
const navMode = localStorage.getItem(NAV_MODE_KEY) || "topbar";
const sidebarBtn = document.getElementById("pref-nav-sidebar");
const topbarBtn = document.getElementById("pref-nav-topbar");
if (sidebarBtn && topbarBtn) {
updateNavLayoutButtons(navMode === "topbar" ? "topbar" : "sidebar", sidebarBtn, topbarBtn);
}
}
async function loadCurrentUser() {
@@ -369,6 +388,7 @@ function savePrefs(event) {
if (prefDesktopNotificationsHint) prefDesktopNotificationsHint.classList.add("hidden");
showMessage("Preferences saved.");
}
}
async function saveSecurityPolicy(event) {
@@ -827,6 +847,22 @@ async function init() {
passwordForm.addEventListener("submit", updatePassword);
prefsForm.addEventListener("submit", savePrefs);
const sidebarBtn = document.getElementById("pref-nav-sidebar");
const topbarBtn = document.getElementById("pref-nav-topbar");
if (sidebarBtn && topbarBtn) {
sidebarBtn.addEventListener("click", () => {
if (localStorage.getItem(NAV_MODE_KEY) === "sidebar") return;
localStorage.setItem(NAV_MODE_KEY, "sidebar");
window.location.reload();
});
topbarBtn.addEventListener("click", () => {
if (localStorage.getItem(NAV_MODE_KEY) === "topbar") return;
localStorage.setItem(NAV_MODE_KEY, "topbar");
window.location.reload();
});
}
securityForm.addEventListener("submit", saveSecurityPolicy);
tlsForm.addEventListener("submit", saveTlsSettings);
tlsCertbotAutoBtn.addEventListener("click", runCertbotAutoSetup);
+14
View File
@@ -209,6 +209,20 @@
class="mt-1 w-full px-3 py-2 rounded-lg bg-slate-900 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-500"
/>
</label>
<div>
<span class="text-sm text-slate-300">Navigation layout</span>
<div class="mt-2 flex gap-2">
<button type="button" id="pref-nav-sidebar"
class="nav-layout-btn flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-lg border text-sm font-medium transition-colors">
<i class="fa-solid fa-table-columns"></i> Sidebar
</button>
<button type="button" id="pref-nav-topbar"
class="nav-layout-btn flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-lg border text-sm font-medium transition-colors">
<i class="fa-solid fa-bars"></i> Top Bar
</button>
</div>
<p class="text-xs text-slate-500 mt-1">Changes apply immediately on save.</p>
</div>
<button
type="submit"
class="px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 transition-colors text-white font-medium"