// ── Context menu data ───────────────────────────────────────────────────────── const MENU_GROUPS = [ { id: "remote-access", label: "Remote Access", icon: "fa-solid fa-plug", color: "text-indigo-400", items: [ { label: "Console", icon: "fa-solid fa-terminal", icolor: "text-emerald-400", open: "console" }, { label: "Remote Desktop", icon: "fa-solid fa-desktop", icolor: "text-purple-400", open: "remotedesktop" }, { label: "Backstage", icon: "fa-solid fa-ghost", icolor: "text-violet-400", open: "Backstage" }, { label: "Voice", icon: "fa-solid fa-headset", icolor: "text-teal-400", open: "voice" }, ], }, { id: "monitoring", label: "Monitoring", icon: "fa-solid fa-eye", color: "text-cyan-400", items: [ { label: "Webcam", icon: "fa-solid fa-video", icolor: "text-emerald-400", open: "webcam" }, { label: "Keylogger", icon: "fa-solid fa-keyboard", icolor: "text-yellow-400", open: "keylogger" }, { label: "Process Manager", icon: "fa-solid fa-list-check", icolor: "text-orange-400", open: "processes" }, ], }, { id: "system", label: "System", icon: "fa-solid fa-server", color: "text-blue-400", items: [ { label: "File Browser", icon: "fa-solid fa-folder-tree", icolor: "text-blue-400", open: "files" }, { label: "Execution", icon: "fa-solid fa-rocket", icolor: "text-cyan-400", open: "silent-exec", id: "menu-silent-exec", hidden: true }, ], }, { id: "agent", label: "Agent", icon: "fa-solid fa-robot", color: "text-slate-400", items: [ { label: "Ping", icon: "fa-solid fa-satellite-dish", icolor: "text-slate-300", action: "ping" }, { label: "Reconnect", icon: "fa-solid fa-rotate", icolor: "text-slate-300", action: "reconnect" }, { label: "Set Nickname", icon: "fa-solid fa-signature", icolor: "text-slate-300", action: "set-nickname" }, { label: "Set Custom Tag", icon: "fa-solid fa-tag", icolor: "text-slate-300", action: "set-custom-tag" }, { divider: true }, { label: "Disconnect", icon: "fa-solid fa-plug-circle-xmark", icolor: "text-red-400", action: "disconnect" }, { label: "Uninstall", icon: "fa-solid fa-trash", icolor: "text-red-300", action: "uninstall" }, { label: "Remove From Dashboard", icon: "fa-solid fa-user-xmark", icolor: "text-rose-300", action: "remove-dashboard" }, ], }, ]; // ── Styles ──────────────────────────────────────────────────────────────────── const menuStyle = document.createElement("style"); menuStyle.textContent = ` #command-menu { position: fixed; display: none; flex-direction: row; align-items: flex-start; z-index: 9999; filter: drop-shadow(0 8px 40px rgba(0,0,0,0.7)); } #ctx-main { background: #141c2b; border: 1px solid rgba(148,163,184,0.14); border-radius: 8px; padding: 4px; min-width: 196px; display: flex; flex-direction: column; gap: 1px; position: relative; } #ctx-sub { background: #141c2b; border: 1px solid rgba(148,163,184,0.14); border-radius: 8px; padding: 4px; min-width: 188px; position: absolute; left: calc(100% + 5px); top: 0; display: none; flex-direction: column; gap: 1px; z-index: 1; } .ctx-row { display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 10px; border-radius: 5px; font-size: 13px; font-weight: 500; color: #cbd5e1; background: transparent; border: none; cursor: pointer; text-align: left; white-space: nowrap; user-select: none; -webkit-user-select: none; transition: background 0.1s, color 0.1s; } .ctx-row:hover, .ctx-row.ctx-active { background: rgba(71,85,105,0.55); color: #f1f5f9; } .ctx-row.ctx-active .ctx-chevron { opacity: 1; } .ctx-item { display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 10px; border-radius: 5px; font-size: 13px; font-weight: 500; color: #cbd5e1; background: transparent; border: none; cursor: pointer; text-align: left; white-space: nowrap; user-select: none; -webkit-user-select: none; transition: background 0.1s, color 0.1s; } .ctx-item:hover:not([disabled]):not([aria-disabled="true"]) { background: rgba(71,85,105,0.55); color: #f1f5f9; } .ctx-item[disabled], .ctx-item[aria-disabled="true"] { opacity: 0.38; cursor: not-allowed; } .ctx-divider { height: 1px; background: rgba(148,163,184,0.13); margin: 3px 4px; } .ctx-sub-panel { display: none; flex-direction: column; gap: 1px; } .ctx-icon { width: 16px; text-align: center; flex-shrink: 0; font-size: 13px; } .ctx-chevron { margin-left: auto; font-size: 10px; opacity: 0.4; transition: opacity 0.1s; flex-shrink: 0; } /* Allow classList.add/remove("hidden") to work on items inside the menu */ #command-menu .hidden { display: none !important; } /* Mobile: stack submenu below the main column */ @media (max-width: 600px) { #command-menu { flex-direction: column; max-width: calc(100vw - 16px); } #ctx-sub { position: static; margin-top: 0; min-width: 0; width: 100%; border-radius: 0 0 8px 8px; border-top: 1px solid rgba(148,163,184,0.08); left: auto; right: auto; } #ctx-main { border-radius: 8px 8px 0 0; } } `; document.head.appendChild(menuStyle); // ── Build DOM ───────────────────────────────────────────────────────────────── function buildItemHTML(item) { if (item.divider) return `
`; const dataAttr = item.open ? `data-open="${item.open}"` : `data-action="${item.action}"`; const idAttr = item.id ? `id="${item.id}"` : ""; const hiddenClass = item.hidden ? " hidden" : ""; return ``; } const mainRowsHTML = MENU_GROUPS.map(g => `` ).join("") + `
` + ``; const subPanelsHTML = MENU_GROUPS.map(g => `
${g.items.map(buildItemHTML).join("")}
` ).join("") + `
`; const menu = document.createElement("div"); menu.id = "command-menu"; menu.setAttribute("role", "menu"); menu.setAttribute("aria-hidden", "true"); menu.innerHTML = `
${mainRowsHTML}
${subPanelsHTML}
`; document.body.appendChild(menu); const ctxMain = menu.querySelector("#ctx-main"); const ctxSub = menu.querySelector("#ctx-sub"); // ── Submenu interaction ─────────────────────────────────────────────────────── let activeGroupId = null; let _hideTimer = null; function showSubmenu(groupId, rowEl) { if (activeGroupId === groupId) return; activeGroupId = groupId; ctxMain.querySelectorAll(".ctx-row").forEach(r => r.classList.remove("ctx-active")); rowEl.classList.add("ctx-active"); ctxSub.querySelectorAll(".ctx-sub-panel").forEach(p => { p.style.display = "none"; }); const panel = ctxSub.querySelector(`[data-for="${groupId}"]`); if (!panel) { ctxSub.style.display = "none"; return; } panel.style.display = "flex"; ctxSub.style.display = "flex"; // Align submenu vertically with the hovered row, then clamp to viewport requestAnimationFrame(() => { const rowRect = rowEl.getBoundingClientRect(); const mainRect = ctxMain.getBoundingClientRect(); let offsetY = rowRect.top - mainRect.top; const subH = ctxSub.offsetHeight; const menuTop = parseFloat(menu.style.top) || 0; if (menuTop + offsetY + subH > window.innerHeight - 8) { offsetY = Math.max(0, window.innerHeight - 8 - subH - menuTop); } ctxSub.style.top = offsetY + "px"; // Flip to left if submenu overflows right edge const subW = ctxSub.offsetWidth; if (mainRect.right + 5 + subW > window.innerWidth - 8) { ctxSub.style.left = "auto"; ctxSub.style.right = "calc(100% + 5px)"; } else { ctxSub.style.left = "calc(100% + 5px)"; ctxSub.style.right = "auto"; } }); } function hideSubmenu() { clearTimeout(_hideTimer); _hideTimer = null; activeGroupId = null; ctxSub.style.display = "none"; ctxSub.querySelectorAll(".ctx-sub-panel").forEach(p => { p.style.display = "none"; }); ctxMain.querySelectorAll(".ctx-row").forEach(r => r.classList.remove("ctx-active")); } function isMobileMenu() { return window.matchMedia("(max-width: 600px)").matches; } ctxMain.querySelectorAll(".ctx-row").forEach(rowEl => { rowEl.addEventListener("mouseenter", () => { if (isMobileMenu()) return; const groupId = rowEl.dataset.groupToggle; if (groupId) showSubmenu(groupId, rowEl); }); }); // Debounced hide — prevents the 5px gap between panels from closing the submenu function scheduleHide() { if (!isMobileMenu()) _hideTimer = setTimeout(hideSubmenu, 150); } function cancelHide() { clearTimeout(_hideTimer); _hideTimer = null; } ctxMain.addEventListener("mouseleave", scheduleHide); ctxMain.addEventListener("mouseenter", cancelHide); ctxSub.addEventListener("mouseenter", cancelHide); ctxSub.addEventListener("mouseleave", scheduleHide); // Touch / mobile: tap to toggle group ctxMain.querySelectorAll(".ctx-row").forEach(rowEl => { rowEl.addEventListener("click", (e) => { const groupId = rowEl.dataset.groupToggle; if (!groupId) return; e.stopPropagation(); if (activeGroupId === groupId) { hideSubmenu(); } else { showSubmenu(groupId, rowEl); } }); }); const modal = document.createElement("div"); modal.className = "modal fixed inset-0 z-40 hidden items-center justify-center bg-black/80 backdrop-blur"; modal.innerHTML = `
preview
`; document.body.appendChild(modal); const modalImg = modal.querySelector("#modal-img"); export function openMenu(clientId, x, y, setContext, options = {}) { if (setContext) setContext(clientId); // Toggle remove-dashboard visibility const removeBtn = menu.querySelector('[data-action="remove-dashboard"]'); if (removeBtn) { removeBtn.style.display = options.isOnline === true ? "none" : ""; } // Reset submenu state hideSubmenu(); // Show menu off-screen first so we can measure, then reposition menu.style.left = "-9999px"; menu.style.top = "-9999px"; menu.style.display = "flex"; menu.setAttribute("aria-hidden", "false"); requestAnimationFrame(() => { const mw = ctxMain.offsetWidth; const mh = ctxMain.offsetHeight; const vw = window.innerWidth; const vh = window.innerHeight; menu.style.left = Math.max(8, Math.min(x, vw - mw - 8)) + "px"; menu.style.top = Math.max(8, Math.min(y, vh - mh - 8)) + "px"; }); } export function closeMenu(clearContext) { menu.style.display = "none"; menu.setAttribute("aria-hidden", "true"); hideSubmenu(); if (clearContext) clearContext(); } export function openModal(src) { if (!src) return; modalImg.src = ""; setTimeout(() => { modalImg.src = src; modal.classList.remove("hidden"); modal.classList.add("flex"); }, 10); } export function closeModal() { modal.classList.remove("flex"); modal.classList.add("hidden"); } export function wireModalClose() { modal.addEventListener("click", closeModal); window.addEventListener("keydown", (e) => { if (e.key === "Escape") { closeModal(); closeMenu(); } }); } export { menu, modal };