// index.js function debounce(fn, delay = 500) { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn.apply(this, args), delay); }; } window.debouncedPopulate = window.debouncedPopulate || debounce(() => populateSystemDropdown(), 300); window.debouncedAclMode = window.debouncedAclMode || debounce(() => checkAclMode(), 300); // const debouncedPopulate = debounce(populateSystemDropdown, 300); // Run AFTER sidebar.html is injected document.addEventListener("sidebarLoaded", () => { hideNameForIndexPage(); waitForLblAndPopulate(); // waits for lblDisplayName3 and triggers populateSystemDropdown waitForDropdownAndCheckAcl(); // ✅ runs ACL AFTER dropdown is populated initAccessModeWatcher(); // radio buttons }); // ================================ // 🔹 Your original functions (unchanged except populate + new helper) // ================================ function waitForDropdownAndCheckAcl() { const observer = new MutationObserver(() => { const dropdown = document.getElementById("systemDropdown"); if (dropdown && dropdown.options.length > 0) { // ✅ wait until options exist observer.disconnect(); // Run ACL check now debouncedAclMode(); // Re-run on dropdown change dropdown.addEventListener("change", () => { localStorage.setItem("currentClient", dropdown.value); debouncedAclMode(); }); } }); observer.observe(document.body, { childList: true, subtree: true }); } function initAccessModeWatcher() { const radios = document.querySelectorAll('input[name="access"]'); if (!radios.length) return; radios.forEach((radio) => { radio.addEventListener("change", () => { // checkAclMode(); // ✅ Re-check ACL immediately on change debouncedAclMode(); }); }); } // ✅ New helper to wait until lblDisplayName2 exists function waitForLblAndPopulate() { const observer = new MutationObserver(() => { const lbl = document.getElementById("lblDisplayName3"); if (lbl && lbl.textContent.trim()) { observer.disconnect(); // populateSystemDropdown(); debouncedPopulate(); } }); observer.observe(document.body, { childList: true, subtree: true }); } // ✅ Helper function to parse JWT token and get roles function getRolesFromToken(token) { if (!token) return []; try { // Use parseJwt from auth.js if available, otherwise parse inline let payload; if (typeof parseJwt === "function") { payload = parseJwt(token); } else { const base64Url = token.split(".")[1]; const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); payload = JSON.parse(atob(base64)); } if (!payload) return []; // Check common role claim names const roles = payload.roles || payload.permissions || []; // Handle both array and space-separated string formats if (Array.isArray(roles)) { return roles; } else if (typeof roles === "string") { return roles.split(" ").filter((r) => r.trim()); } return []; } catch (error) { console.error("Error parsing JWT token:", error); return []; } } async function populateSystemDropdown() { const dropdown = document.getElementById("systemDropdown"); if (!dropdown) return; // ✅ Get token from cookie const cookieName = env.FRONTEND_APP_NAME + "AccessToken"; const tokenMatch = document.cookie.match( new RegExp("(^| )" + cookieName + "=([^;]+)"), ); const token = tokenMatch ? decodeURIComponent(tokenMatch[2]) : null; if (!token) { console.error("❌ No access token found in cookie:", cookieName); return; } // ✅ Extract roles from JWT const roles = getRolesFromToken(token); // Only superadmin roles, extract clientid const clientIds = [ ...new Set( roles .filter((r) => r.endsWith(":superadmin")) .map((r) => r.split(":")[0]), ), ]; // Clear dropdown first dropdown.innerHTML = ""; if (clientIds.length === 0) { const opt = document.createElement("option"); opt.textContent = "No clients available"; opt.disabled = true; dropdown.appendChild(opt); return; } // // ✅ Fetch displaynames for all clientIds in parallel // const fetchPromises = clientIds.map((clientId) => // fetch(`${env.CLIENTDETAIL_ENDPOINT}_search`, { // method: "POST", // headers: { // "Content-Type": "application/json", // Authorization: `Bearer ${token}`, // }, // body: JSON.stringify({ clientid: clientId }), // }) // .then((res) => (res.ok ? res.json() : null)) // .then((data) => ({ // clientId, // displayname: data?.results?.[0]?.displayname || clientId, // })) // .catch(() => ({ clientId, displayname: clientId })) // ); // const clientData = await Promise.all(fetchPromises); // Populate dropdown clientIds.forEach((clientId) => { const opt = document.createElement("option"); opt.value = clientId; opt.textContent = clientId; dropdown.appendChild(opt); }); // Select the first system by default if (dropdown.options.length > 0) dropdown.selectedIndex = 0; // Trigger any updates after dropdown population setSystemDropdown(); } async function checkAclMode() { const dropdown = document.getElementById("systemDropdown"); if (!dropdown) return; const clientid = dropdown.value; // /console.log(clientid); // const clientid = env.CLIENT_ID; // 🔹 Check if a radio button is selected const selectedRadio = document.querySelector('input[name="access"]:checked'); let usermode = selectedRadio ? selectedRadio.dataset.mode : null; const ok = await checkToken(); if (!ok) return; // ⛔ prevent API call when refresh failed // 🔹 If no radio is selected, fall back to API if (usermode === null) { function getCookie(name) { const match = document.cookie.match( new RegExp("(^| )" + name + "=([^;]+)"), ); return match ? decodeURIComponent(match[2]) : null; } const cookieName = env.FRONTEND_APP_NAME + "AccessToken"; const token = getCookie(cookieName); if (!token) { console.warn("No access token found in cookie:", cookieName); return; } try { const response = await fetch(`${env.ACLMODE_ENDPOINT}_search`, { method: "POST", headers: { Accept: "*/*", "Content-Type": "application/json", Authorization: "Bearer " + token, }, body: JSON.stringify({ clientid }), }); if (!response.ok) { console.error("API call failed:", response.status); return; } const data = await response.json(); if (!data.results || data.results.length === 0) return; usermode = data.results[0].usermode; } catch (err) { console.error("Error calling ACL Mode API:", err); return; } } // 🔹 Apply rules const authMenu = document.querySelector( '.reports1.has-submenu[data-page="authentication-security"]', ); const accessSettingItem = document.querySelector( '.submenu-item[data-page="settings"]', ); const uamParent = document.querySelector( '.submenu-item.has-submenu[data-page="user-access-management"]', ); const blacklistItem = document.querySelector( '.submenu-item[data-page="blacklist"]', ); const whitelistItem = document.querySelector( '.submenu-item[data-page="whitelist"]', ); // 🔄 Reset visibility first blacklistItem?.classList.remove("hidden"); whitelistItem?.classList.remove("hidden"); uamParent?.classList.remove("disabled"); // 🔐 Apply rules if (usermode === "") { // Allow all → disable UAM completely uamParent?.classList.add("disabled"); // Optional: collapse nested submenu if open const nested = uamParent?.nextElementSibling; if (nested) { nested.classList.remove("open"); nested.style.maxHeight = null; } } else if (usermode === "blacklist") { // Show only blacklist whitelistItem?.classList.add("hidden"); } else if (usermode === "whitelist") { // Show only whitelist blacklistItem?.classList.add("hidden"); } } document.addEventListener("click", function (e) { const disabledItem = e.target.closest(".disabled"); if (disabledItem) { e.preventDefault(); e.stopPropagation(); } }); function hideNameForIndexPage() { let currentPageName = window.location.pathname.split("/").pop(); if (currentPageName === "" || currentPageName === "/") { currentPageName = "index.html"; } if (currentPageName === "index.html") { const lbl = document.getElementById("name"); if (lbl) { lbl.style.display = "none"; } } } // 🔹 Handles defaultClient + currentClient logic function setSystemDropdown() { const dropdown = document.getElementById("systemDropdown"); if (!dropdown) return; const savedCurrent = localStorage.getItem("currentClient"); const savedDefault = localStorage.getItem("defaultClient"); let valueToUse = null; if ( savedCurrent && Array.from(dropdown.options).some((opt) => opt.value === savedCurrent) ) { valueToUse = savedCurrent; // ✅ use currentClient if valid } else if ( savedDefault && Array.from(dropdown.options).some((opt) => opt.value === savedDefault) ) { valueToUse = savedDefault; // ✅ fallback to defaultClient } else { valueToUse = dropdown.value; // ✅ fallback to whatever is there } dropdown.value = valueToUse; localStorage.setItem("currentClient", valueToUse); // Always sync to currentClient dropdown.dispatchEvent(new Event("change")); // Trigger downstream updates } function initSidebar() { const sidebar = document.getElementById("sidebarWrapper"); const overlay = document.getElementById("overlay"); const toggleButton = document.getElementById("toggleButton"); if (!sidebar || !overlay || !toggleButton) return; // ===== Sidebar toggle functions ===== function openSidebar() { sidebar.classList.remove("minimized"); sidebar.classList.add("open"); overlay.classList.add("active"); document.body.classList.add("sidebar-open"); document.body.classList.remove("sidebar-minimized"); localStorage.setItem("sidebarState", "open"); // ✅ save state restoreActiveMenu(); } function minimizeSidebar() { const sidebar = document.getElementById("sidebarWrapper"); const overlay = document.getElementById("overlay"); // Close all top-level submenus document.querySelectorAll(".reports1.has-submenu").forEach((parent) => { parent.classList.remove("open"); const submenu = parent.nextElementSibling; if (submenu) { submenu.classList.remove("open"); submenu.style.maxHeight = "0"; // Close all nested submenus inside this top-level menu submenu.querySelectorAll(".submenu-nested").forEach((nested) => { nested.classList.remove("open"); nested.style.maxHeight = "0"; }); } restoreActiveMenu(); }); // Close all nested submenus in general (if any left open) document.querySelectorAll(".submenu-nested.open").forEach((nested) => { nested.classList.remove("open"); nested.style.maxHeight = "0"; }); // Sidebar class toggles sidebar.classList.remove("open"); sidebar.classList.add("minimized"); overlay.classList.remove("active"); document.body.classList.remove("sidebar-open"); document.body.classList.add("sidebar-minimized"); localStorage.setItem("sidebarState", "minimized"); // ✅ save state } function toggleSidebar() { if (sidebar.classList.contains("open")) { minimizeSidebar(); } else { openSidebar(); } } toggleButton.addEventListener("click", toggleSidebar); // ===== Restore active menu ===== function restoreActiveMenu() { const pageName = window.location.pathname .split("/") .pop() .replace(".html", ""); const activeItem = document.querySelector( `.submenu-item[data-page="${pageName}"]`, ); if (!activeItem) return; // Highlight the active item activeItem.classList.add("active"); // console.log( // "Active item:", // activeItem.tagName, // activeItem.className, // activeItem.dataset.page, // ); // Walk up parents and open all .submenu or .submenu-nested let parent = activeItem.parentElement; while (parent && parent.id !== "sidebarWrapper") { if ( parent.classList.contains("submenu") || parent.classList.contains("submenu-nested") ) { parent.classList.add("open"); parent.style.maxHeight = parent.scrollHeight + "px"; // console.log("Opened parent submenu:", parent.tagName, parent.className); } // Open top-level menu if previous sibling has .has-submenu const prev = parent.previousElementSibling; if (prev && prev.classList.contains("has-submenu")) { prev.classList.add("open"); // console.log("Opened top-level parent:", prev.tagName, prev.className); } parent = parent.parentElement; } } restoreActiveMenu(); // ===== Top-level submenu toggle ===== document.querySelectorAll(".reports1.has-submenu").forEach((parent) => { const submenu = parent.nextElementSibling; parent.addEventListener("click", () => { if (sidebar.classList.contains("minimized")) openSidebar(); const isOpen = parent.classList.contains("open"); // Close all menus document.querySelectorAll(".reports1.has-submenu").forEach((p) => { const sm = p.nextElementSibling; p.classList.remove("open"); if (sm) { sm.classList.remove("open"); sm.style.maxHeight = null; sm.querySelectorAll(".submenu-nested").forEach((nested) => { nested.classList.remove("open"); nested.style.maxHeight = null; }); } }); // If it was closed → open it if (!isOpen) { parent.classList.add("open"); if (submenu) { submenu.classList.add("open"); submenu.style.maxHeight = submenu.scrollHeight + "px"; } } }); }); // ===== Nested submenu toggle ===== document.querySelectorAll(".submenu-item.has-submenu").forEach((parent) => { const nested = parent.nextElementSibling; if (!nested) return; parent.addEventListener("click", (e) => { e.stopPropagation(); const topParent = parent.closest(".submenu"); const isOpen = nested.classList.toggle("open"); nested.style.display = "flex"; nested.style.maxHeight = isOpen ? nested.scrollHeight + "px" : "0"; // logMenuState(`Toggled nested (${isOpen ? "open" : "closed"})`, parent); if (topParent) { let totalHeight = 0; Array.from(topParent.children).forEach((c) => { if (c !== nested) totalHeight += c.scrollHeight; else if (isOpen) totalHeight += nested.scrollHeight; }); topParent.style.maxHeight = 120 + "px"; // logMenuState("Adjusted top submenu height", topParent); } }); }); // ===== Highlight active page ===== let pageName = window.location.pathname.split("/").pop().replace(".html", ""); // ✅ Special case: userProfile.html should highlight Users Directory if (pageName === "userProfile") pageName = "users-directory"; // ===== ALSO ensure top-level parent is marked active even when minimized ===== const submenuItem = document.querySelector( `.submenu-item[data-page="${pageName}"]`, ); if (submenuItem) { const topParent = submenuItem.closest(".submenu")?.previousElementSibling; if (topParent?.classList.contains("has-submenu")) { topParent.classList.add("active"); } } const activeItem = document.querySelector( `.submenu-item[data-page="${pageName}"]`, ); if (activeItem) { activeItem.classList.add("active"); // logMenuState("Active item", activeItem); // Open parent submenu const topParent = activeItem.closest(".submenu"); const topLevelParent = topParent?.previousElementSibling; if (topParent && topLevelParent) { topParent.classList.add("open"); topParent.style.maxHeight = topParent.scrollHeight + "px"; topLevelParent.classList.add("open"); // logMenuState("Top-level parent", topLevelParent); // logMenuState("Top submenu", topParent); } // Open all nested parents containing the active item let nestedParent = activeItem.closest(".submenu-nested"); while (nestedParent) { nestedParent.classList.add("open"); nestedParent.style.maxHeight = nestedParent.scrollHeight + "px"; // Highlight only the active submenu-item inside nestedParent.querySelectorAll(".submenu-item").forEach((item) => { if (item === activeItem) { item.classList.add("active"); } else { item.classList.remove("active"); } }); // ✅ Also highlight the top-level .has-submenu parent const topLevel = nestedParent.previousElementSibling; if (topLevel?.classList.contains("has-submenu")) { topLevel.classList.add("active"); } nestedParent = nestedParent.parentElement.closest(".submenu-nested"); } } // Highlight top-level items without submenu const topActive = document.querySelector( `.reports1[data-page="${pageName}"]`, ); if (topActive) { topActive.classList.add("active"); // logMenuState("Top-level no submenu", topActive); } if (typeof applyUserAccessRestrictions === "function") { applyUserAccessRestrictions(); } function openNestedParents(el) { let parent = el.closest(".submenu-nested"); while (parent) { parent.classList.add("open"); parent.style.maxHeight = parent.scrollHeight + "px"; parent = parent.parentElement.closest(".submenu-nested"); } } function logMenuState(label, el) { if (!el) return; console.log( `${label}:`, el.tagName, el.className, el.dataset.page || el.textContent.trim(), ); } // ===== Restore sidebar state ===== const savedState = localStorage.getItem("sidebarState"); if (savedState === "open") { openSidebar(); } else { minimizeSidebar(); } } function safeInitSystemDropdown() { const dropdown = document.getElementById("systemDropdown"); if (dropdown) { // populateSystemDropdown(); // sidebar already injected debouncedPopulate(); } else { document.addEventListener("sidebarLoaded", () => { // populateSystemDropdown(); // wait for sidebar debouncedPopulate(); }); } } // Call this instead of populateSystemDropdown() directly safeInitSystemDropdown();