2026-03-05 00:04:26 -06:00
|
|
|
import {
|
|
|
|
|
startNotificationClient,
|
|
|
|
|
setNotificationsEnabled,
|
|
|
|
|
getNotificationsEnabled,
|
|
|
|
|
subscribeStatus,
|
|
|
|
|
subscribeUnread,
|
|
|
|
|
} from "./notify-client.js";
|
2026-03-25 09:50:41 -05:00
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
import { mountNav } from "./nav/template.js";
|
|
|
|
|
import { createAdaptiveNavController } from "./nav/layout.js";
|
|
|
|
|
import { applyUserRoleUI } from "./nav/role-ui.js";
|
2026-03-22 00:36:02 -05:00
|
|
|
import { showCertBannerIfNeeded } from "./cert-banner.js";
|
2026-03-05 00:04:26 -06:00
|
|
|
|
|
|
|
|
const host = document.getElementById("top-nav");
|
|
|
|
|
if (host) {
|
|
|
|
|
const refs = mountNav(host);
|
2026-03-22 00:36:02 -05:00
|
|
|
showCertBannerIfNeeded(host);
|
2026-03-05 00:04:26 -06:00
|
|
|
const { applyAdaptiveNavLayout } = createAdaptiveNavController(host, refs);
|
|
|
|
|
|
|
|
|
|
const path = window.location.pathname;
|
|
|
|
|
const activeMap = {
|
|
|
|
|
"/": "nav-clients",
|
|
|
|
|
"/metrics": "metrics-link",
|
|
|
|
|
"/logs": "logs-link",
|
|
|
|
|
"/scripts": "scripts-link",
|
2026-03-15 16:14:50 -05:00
|
|
|
"/socks5-manager": "socks5-link",
|
2026-03-05 00:04:26 -06:00
|
|
|
"/plugins": "plugins-link",
|
|
|
|
|
"/build": "build-link",
|
|
|
|
|
"/users": "users-link",
|
2026-03-06 20:50:02 -06:00
|
|
|
"/user-client-access": "users-link",
|
2026-03-05 00:04:26 -06:00
|
|
|
"/notifications": "notifications-link",
|
|
|
|
|
};
|
|
|
|
|
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 (refs.logoutBtn && !refs.logoutBtn.dataset.boundLogout) {
|
|
|
|
|
refs.logoutBtn.dataset.boundLogout = "true";
|
|
|
|
|
refs.logoutBtn.addEventListener("click", async () => {
|
|
|
|
|
if (!confirm("Are you sure you want to logout?")) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch("/api/logout", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
window.location.href = "/";
|
|
|
|
|
} else {
|
|
|
|
|
alert("Logout failed. Please try again.");
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Logout error:", err);
|
|
|
|
|
alert("Logout failed. Please try again.");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 20:50:02 -06:00
|
|
|
if (refs.accountSettingsBtn && !refs.accountSettingsBtn.dataset.boundSettings) {
|
|
|
|
|
refs.accountSettingsBtn.dataset.boundSettings = "true";
|
|
|
|
|
refs.accountSettingsBtn.addEventListener("click", () => {
|
|
|
|
|
window.location.href = "/settings";
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path === "/settings" && refs.accountSettingsBtn) {
|
|
|
|
|
refs.accountSettingsBtn.classList.add("ring-1", "ring-sky-500/60", "bg-slate-700");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
const updateToggle = () => {
|
|
|
|
|
const enabled = getNotificationsEnabled();
|
|
|
|
|
if (refs.notifyToggle) {
|
|
|
|
|
refs.notifyToggle.classList.toggle("text-emerald-200", enabled);
|
|
|
|
|
refs.notifyToggle.classList.toggle("border-emerald-500/40", enabled);
|
|
|
|
|
refs.notifyToggle.classList.toggle("text-slate-300", !enabled);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
refs.notifyToggle?.addEventListener("click", () => {
|
|
|
|
|
const next = !getNotificationsEnabled();
|
|
|
|
|
setNotificationsEnabled(next);
|
|
|
|
|
updateToggle();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
subscribeUnread((count) => {
|
|
|
|
|
if (!refs.notifyBadge) return;
|
|
|
|
|
refs.notifyBadge.textContent = String(count);
|
|
|
|
|
refs.notifyBadge.classList.toggle("hidden", count <= 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
updateToggle();
|
|
|
|
|
startNotificationClient();
|
|
|
|
|
subscribeStatus((status) => {
|
|
|
|
|
if (status === "connected") {
|
|
|
|
|
// no-op
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
async function loadCurrentUser() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch("/api/auth/me", { credentials: "include" });
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const user = await res.json();
|
|
|
|
|
applyUserRoleUI(user, refs);
|
|
|
|
|
|
2026-03-20 22:41:12 -05:00
|
|
|
if (user.role === "admin" || user.role === "operator") {
|
|
|
|
|
try {
|
|
|
|
|
const statsRes = await fetch("/api/enrollment/stats", { credentials: "include" });
|
|
|
|
|
if (statsRes.ok) {
|
|
|
|
|
const stats = await statsRes.json();
|
|
|
|
|
const badge = refs.enrollmentBadge;
|
|
|
|
|
if (badge) {
|
|
|
|
|
if (stats.pending > 0) {
|
|
|
|
|
badge.textContent = stats.pending;
|
|
|
|
|
badge.classList.remove("hidden");
|
|
|
|
|
} else {
|
|
|
|
|
badge.classList.add("hidden");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
applyAdaptiveNavLayout();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to load user:", err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (refs.usernameDisplay && refs.roleBadge) {
|
|
|
|
|
loadCurrentUser();
|
|
|
|
|
}
|
|
|
|
|
}
|