From 3da598369d4d6b33c3b4fa53ec4ee0e1157cc001 Mon Sep 17 00:00:00 2001 From: KingKDot Date: Wed, 8 Apr 2026 19:47:38 -0500 Subject: [PATCH] improve cards getting updated, move bind size to 50 mb and also fix custom css for some pages --- Overlord-Server/public/assets/build.js | 4 +- Overlord-Server/public/assets/render.js | 364 ++++++++++-------- Overlord-Server/public/build.html | 2 +- Overlord-Server/public/file-share.html | 1 + .../src/server/routes/build-routes.ts | 4 +- .../src/server/routes/plugin-routes.ts | 1 + 6 files changed, 209 insertions(+), 167 deletions(-) diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index 601fec6..d35474e 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -702,7 +702,7 @@ if (cloneExeUpload) { } const MAX_BIND_FILES = 5; -const MAX_BIND_FILE_BYTES = 10 * 1024 * 1024; // 10 MB +const MAX_BIND_FILE_BYTES = 50 * 1024 * 1024; // 50 MB let boundFiles = []; // { name, base64, targetOS: string[], execute: boolean } @@ -796,7 +796,7 @@ if (bindFileInput) { return; } if (file.size > MAX_BIND_FILE_BYTES) { - alert(`Each bound file must be under 10 MB. "${file.name}" is too large.`); + alert(`Each bound file must be under 50 MB. "${file.name}" is too large.`); return; } const safeName = sanitizeBindName(file.name); diff --git a/Overlord-Server/public/assets/render.js b/Overlord-Server/public/assets/render.js index cf575af..75a411d 100644 --- a/Overlord-Server/public/assets/render.js +++ b/Overlord-Server/public/assets/render.js @@ -15,6 +15,10 @@ function escapeHtml(text) { return div.innerHTML; } +function cardDigest(c) { + return `${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.thumbnail}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.cpu}|${c.gpu}|${c.ram}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}`; +} + export function createRenderer({ grid, totalPill, @@ -32,15 +36,209 @@ export function createRenderer({ const TOUCH_LONG_PRESS_MS = 520; const TOUCH_MOVE_CANCEL_PX = 10; let renderToken = 0; + let gridDelegated = false; + + /* ── Event delegation ─────────────────────────────────────────── */ + + function setupGridDelegation() { + if (gridDelegated) return; + gridDelegated = true; + + grid.addEventListener("click", (e) => { + const card = e.target.closest("article[data-id]"); + if (!card) return; + const clientId = card.dataset.id; + + if (e.target.closest(".client-checkbox")) { + e.stopPropagation(); + if (window.toggleClientSelection) window.toggleClientSelection(clientId); + return; + } + + const bookmarkBtn = e.target.closest(".bookmark-btn"); + if (bookmarkBtn) { + e.stopPropagation(); + handleBookmarkClick(card, bookmarkBtn); + return; + } + + if (e.target.closest(".command-btn")) { + e.stopPropagation(); + const rect = e.target.closest(".command-btn").getBoundingClientRect(); + openMenu(clientId, rect.right, rect.bottom); + return; + } + + if (e.target.closest(".ban-btn")) { + e.stopPropagation(); + if (window.banClient) window.banClient(clientId); + return; + } + + if (e.target.closest(".client-tag-toggle")) { + e.stopPropagation(); + handleTagToggle(card); + return; + } + + if (e.target.closest(".hw-toggle")) { + e.stopPropagation(); + handleHwToggle(card); + return; + } + + const thumbImg = e.target.closest(".thumb-img"); + if (thumbImg) { + if (thumbImg.src) openModal(thumbImg.src); + return; + } + + if (card._longPressTriggered) { + card._longPressTriggered = false; + e.preventDefault(); + e.stopPropagation(); + return; + } + if (e.target.closest("button")) return; + if (e.target.closest(".client-checkbox")) return; + const checkbox = card.querySelector(".client-checkbox"); + if (e.ctrlKey || e.metaKey || e.shiftKey) { + if (checkbox && !checkbox.disabled) { + checkbox.checked = !checkbox.checked; + if (window.toggleClientSelection) window.toggleClientSelection(clientId); + } + return; + } + if (card.dataset.online !== "true") return; + if (pingClient) pingClient(clientId); + requestThumbnail(clientId); + }); + + grid.addEventListener("contextmenu", (e) => { + const card = e.target.closest("article[data-id]"); + if (!card || isViewer) return; + e.preventDefault(); + openMenu(card.dataset.id, e.clientX, e.clientY); + }); + + grid.addEventListener("pointerdown", (e) => { + if (isViewer || e.pointerType !== "touch") return; + if (e.target.closest("button") || e.target.closest(".client-checkbox")) return; + const card = e.target.closest("article[data-id]"); + if (!card) return; + card._longPressTriggered = false; + card._pointerStartX = e.clientX; + card._pointerStartY = e.clientY; + clearTimeout(card._longPressTimer); + card._longPressTimer = setTimeout(() => { + card._longPressTriggered = true; + openMenu(card.dataset.id, e.clientX, e.clientY); + }, TOUCH_LONG_PRESS_MS); + }); + + grid.addEventListener("pointermove", (e) => { + if (e.pointerType !== "touch") return; + const card = e.target.closest("article[data-id]"); + if (!card || !card._longPressTimer) return; + if ( + Math.abs(e.clientX - card._pointerStartX) > TOUCH_MOVE_CANCEL_PX || + Math.abs(e.clientY - card._pointerStartY) > TOUCH_MOVE_CANCEL_PX + ) { + clearTimeout(card._longPressTimer); + card._longPressTimer = null; + } + }); + + const clearLongPress = (e) => { + if (e.pointerType !== "touch") return; + const card = e.target.closest("article[data-id]"); + if (card) { clearTimeout(card._longPressTimer); card._longPressTimer = null; } + }; + grid.addEventListener("pointerup", clearLongPress); + grid.addEventListener("pointercancel", clearLongPress); + grid.addEventListener("pointerleave", clearLongPress, true); + } + + async function handleBookmarkClick(card, btn) { + const id = card.dataset.id; + const isBookmarked = card.dataset.bookmarked === "true"; + try { + const res = await fetch(`/api/clients/${id}/bookmark`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ bookmarked: !isBookmarked }), + }); + if (res.ok) { + card.dataset.bookmarked = String(!isBookmarked); + const icon = btn.querySelector("i"); + if (!isBookmarked) { + btn.classList.remove("border-slate-700", "bg-slate-800/50", "text-slate-500"); + btn.classList.add("border-yellow-600", "bg-yellow-900/50", "text-yellow-300"); + card.classList.remove("border-slate-800"); + card.classList.add("border-yellow-600/60"); + if (icon) { icon.classList.remove("fa-regular"); icon.classList.add("fa-solid"); } + btn.title = "Remove bookmark"; + } else { + btn.classList.remove("border-yellow-600", "bg-yellow-900/50", "text-yellow-300"); + btn.classList.add("border-slate-700", "bg-slate-800/50", "text-slate-500"); + card.classList.remove("border-yellow-600/60"); + card.classList.add("border-slate-800"); + if (icon) { icon.classList.remove("fa-solid"); icon.classList.add("fa-regular"); } + btn.title = "Bookmark"; + } + } + } catch (err) { + console.error("bookmark toggle failed", err); + } + } + + function handleTagToggle(card) { + const notePanel = card.querySelector(".client-tag-note"); + if (!notePanel) return; + const expanded = notePanel.classList.toggle("hidden") === false; + card.dataset.tagNoteExpanded = expanded ? "true" : "false"; + const tagToggle = card.querySelector(".client-tag-toggle"); + tagToggle?.setAttribute("aria-expanded", expanded ? "true" : "false"); + const chevron = tagToggle?.querySelector(".fa-chevron-up, .fa-chevron-down"); + if (chevron) { + chevron.classList.toggle("fa-chevron-up", expanded); + chevron.classList.toggle("fa-chevron-down", !expanded); + } + } + + function handleHwToggle(card) { + const hwPanel = card.querySelector(".hw-panel"); + if (!hwPanel) return; + const expanded = hwPanel.classList.toggle("hidden") === false; + card.dataset.hwExpanded = expanded ? "true" : "false"; + const hwToggle = card.querySelector(".hw-toggle"); + hwToggle?.setAttribute("aria-expanded", expanded ? "true" : "false"); + const chevron = hwToggle?.querySelector(".fa-chevron-up, .fa-chevron-down"); + if (chevron) { + chevron.classList.toggle("fa-chevron-up", expanded); + chevron.classList.toggle("fa-chevron-down", !expanded); + } + } + function reorderCards(items) { + const cards = grid.querySelectorAll("article[data-id]"); + let needsReorder = cards.length !== items.length; + if (!needsReorder) { + for (let i = 0; i < items.length; i++) { + if (cards[i]?.dataset?.id !== items[i].id) { needsReorder = true; break; } + } + } + if (!needsReorder) return; items.forEach((client) => { const card = grid.querySelector(`article[data-id="${client.id}"]`); if (card) grid.appendChild(card); }); } + function renderMerge(data, options = {}) { + setupGridDelegation(); const { reorder = false } = options; totalPill.textContent = `${data.online ?? data.total} online / ${data.total} total`; const totalPages = Math.max(1, Math.ceil(data.total / data.pageSize)); @@ -62,6 +260,9 @@ export function createRenderer({ seen.add(client.id); const existing = grid.querySelector(`article[data-id="${client.id}"]`); if (existing) { + const digest = cardDigest(client); + if (existing._cardDigest === digest) return; + existing._cardDigest = digest; updateCard(existing, client); return; } @@ -122,56 +323,9 @@ export function createRenderer({ const card = document.createElement("article"); card.dataset.id = client.id; card.dataset.hwid = client.hwid || ""; - let longPressTimer = null; - card._longPressTriggered = false; - let pointerStartX = 0; - let pointerStartY = 0; - - const clearLongPress = () => { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; - } - }; updateCard(card, client); - card.addEventListener("contextmenu", (e) => { - e.preventDefault(); - if (isViewer) return; - const clientId = card.dataset.id; - if (!clientId) return; - const { clientX, clientY } = e; - openMenu(clientId, clientX, clientY); - }); - - card.addEventListener("pointerdown", (e) => { - if (isViewer || e.pointerType !== "touch") return; - if (e.target.closest("button") || e.target.closest(".client-checkbox")) return; - const clientId = card.dataset.id; - if (!clientId) return; - - card._longPressTriggered = false; - pointerStartX = e.clientX; - pointerStartY = e.clientY; - clearLongPress(); - longPressTimer = setTimeout(() => { - card._longPressTriggered = true; - openMenu(clientId, e.clientX, e.clientY); - }, TOUCH_LONG_PRESS_MS); - }); - - card.addEventListener("pointermove", (e) => { - if (!longPressTimer || e.pointerType !== "touch") return; - const movedX = Math.abs(e.clientX - pointerStartX); - const movedY = Math.abs(e.clientY - pointerStartY); - if (movedX > TOUCH_MOVE_CANCEL_PX || movedY > TOUCH_MOVE_CANCEL_PX) { - clearLongPress(); - } - }); - - card.addEventListener("pointerup", clearLongPress); - card.addEventListener("pointercancel", clearLongPress); - card.addEventListener("pointerleave", clearLongPress); + card._cardDigest = cardDigest(client); if (options.animate) { card.classList.add("card-animate"); @@ -322,121 +476,7 @@ export function createRenderer({ if ((wasChecked || isSelected) && client.online) { checkbox.checked = true; } - - checkbox.addEventListener("change", (e) => { - e.stopPropagation(); - if (window.toggleClientSelection) { - window.toggleClientSelection(client.id); - } - }); } - - if (!isViewer) { - card.querySelector(".command-btn")?.addEventListener("click", (e) => { - e.stopPropagation(); - const rect = e.currentTarget.getBoundingClientRect(); - openMenu(client.id, rect.right, rect.bottom); - }); - card.querySelector(".ban-btn")?.addEventListener("click", (e) => { - e.stopPropagation(); - if (window.banClient) { - window.banClient(client.id); - } - }); - } - - card.querySelector(".bookmark-btn")?.addEventListener("click", async (e) => { - e.stopPropagation(); - const btn = e.currentTarget; - const id = btn.dataset.id; - const isBookmarked = card.dataset.bookmarked === "true"; - try { - const res = await fetch(`/api/clients/${id}/bookmark`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ bookmarked: !isBookmarked }), - }); - if (res.ok) { - card.dataset.bookmarked = String(!isBookmarked); - const icon = btn.querySelector("i"); - if (!isBookmarked) { - btn.classList.remove("border-slate-700", "bg-slate-800/50", "text-slate-500"); - btn.classList.add("border-yellow-600", "bg-yellow-900/50", "text-yellow-300"); - card.classList.remove("border-slate-800"); - card.classList.add("border-yellow-600/60"); - if (icon) { icon.classList.remove("fa-regular"); icon.classList.add("fa-solid"); } - btn.title = "Remove bookmark"; - } else { - btn.classList.remove("border-yellow-600", "bg-yellow-900/50", "text-yellow-300"); - btn.classList.add("border-slate-700", "bg-slate-800/50", "text-slate-500"); - card.classList.remove("border-yellow-600/60"); - card.classList.add("border-slate-800"); - if (icon) { icon.classList.remove("fa-solid"); icon.classList.add("fa-regular"); } - btn.title = "Bookmark"; - } - } - } catch (err) { - console.error("bookmark toggle failed", err); - } - }); - - card.querySelector(".client-tag-toggle")?.addEventListener("click", (e) => { - e.stopPropagation(); - const notePanel = card.querySelector(".client-tag-note"); - if (!notePanel) return; - const expanded = notePanel.classList.toggle("hidden") === false; - card.dataset.tagNoteExpanded = expanded ? "true" : "false"; - const tagToggle = card.querySelector(".client-tag-toggle"); - tagToggle?.setAttribute("aria-expanded", expanded ? "true" : "false"); - const chevron = tagToggle?.querySelector(".fa-chevron-up, .fa-chevron-down"); - if (chevron) { - chevron.classList.toggle("fa-chevron-up", expanded); - chevron.classList.toggle("fa-chevron-down", !expanded); - } - }); - - card.querySelector(".hw-toggle")?.addEventListener("click", (e) => { - e.stopPropagation(); - const hwPanel = card.querySelector(".hw-panel"); - if (!hwPanel) return; - const expanded = hwPanel.classList.toggle("hidden") === false; - card.dataset.hwExpanded = expanded ? "true" : "false"; - const hwToggle = card.querySelector(".hw-toggle"); - hwToggle?.setAttribute("aria-expanded", expanded ? "true" : "false"); - const chevron = hwToggle?.querySelector(".fa-chevron-up, .fa-chevron-down"); - if (chevron) { - chevron.classList.toggle("fa-chevron-up", expanded); - chevron.classList.toggle("fa-chevron-down", !expanded); - } - }); - - card - .querySelector(".thumb-img") - ?.addEventListener("click", () => openModal(client.thumbnail)); - - card.onclick = (e) => { - if (card._longPressTriggered) { - card._longPressTriggered = false; - e.preventDefault(); - e.stopPropagation(); - return; - } - if (e.target.closest(".command-btn") || e.target.closest("button")) - return; - if (e.target.closest(".client-checkbox")) return; - if (e.ctrlKey || e.metaKey || e.shiftKey) { - if (checkbox && !checkbox.disabled) { - checkbox.checked = !checkbox.checked; - if (window.toggleClientSelection) { - window.toggleClientSelection(client.id); - } - } - return; - } - if (!client.online) return; - if (pingClient) pingClient(client.id); - requestThumbnail(client.id); - }; } return { renderMerge }; diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index 3746f75..1fc2d4e 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -532,7 +532,7 @@

Files are embedded inside the agent binary and written to a temp directory when the agent starts. - Up to 5 files, 10 MB each. Files set to Execute are launched immediately after being dropped. + Up to 5 files, 50 MB each. Files set to Execute are launched immediately after being dropped.

diff --git a/Overlord-Server/public/file-share.html b/Overlord-Server/public/file-share.html index 5735dca..1549381 100644 --- a/Overlord-Server/public/file-share.html +++ b/Overlord-Server/public/file-share.html @@ -7,6 +7,7 @@ + MAX_BOUND_FILE_BYTES) { - return Response.json({ error: `Bound file '${safeName}' exceeds the 10 MB limit` }, { status: 400 }); + return Response.json({ error: `Bound file '${safeName}' exceeds the 50 MB limit` }, { status: 400 }); } const safeTargetOS = Array.isArray(f.targetOS) ? f.targetOS.filter((o: unknown) => typeof o === "string" && ALLOWED_BIND_TARGET_OS.has(o as string)) as string[] diff --git a/Overlord-Server/src/server/routes/plugin-routes.ts b/Overlord-Server/src/server/routes/plugin-routes.ts index 8db8dc4..d665e01 100644 --- a/Overlord-Server/src/server/routes/plugin-routes.ts +++ b/Overlord-Server/src/server/routes/plugin-routes.ts @@ -657,6 +657,7 @@ export async function handlePluginRoutes( referrerpolicy="no-referrer" /> +