Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 39s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 53s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 1m17s
191 lines
5.9 KiB
JavaScript
191 lines
5.9 KiB
JavaScript
const output = document.querySelector("#run-output");
|
|
|
|
if (output) {
|
|
let refreshTimer;
|
|
const refresh = async () => {
|
|
const response = await fetch(output.dataset.outputUrl, { cache: "no-store" });
|
|
if (response.ok) {
|
|
const nextOutput = (await response.json()).output;
|
|
if (nextOutput !== output.textContent) {
|
|
output.textContent = nextOutput;
|
|
}
|
|
}
|
|
const status = document.querySelector("#run-status");
|
|
if (status) {
|
|
const stateResponse = await fetch(status.dataset.stateUrl, { cache: "no-store" });
|
|
if (stateResponse.ok) {
|
|
const state = await stateResponse.json();
|
|
status.textContent = state.status;
|
|
if (state.status !== "queued" && state.status !== "running") {
|
|
window.clearInterval(refreshTimer);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
refresh();
|
|
refreshTimer = window.setInterval(refresh, 2000);
|
|
}
|
|
|
|
const refreshArtifacts = async (container) => {
|
|
const response = await fetch(container.dataset.artifactsUrl, { cache: "no-store" });
|
|
if (!response.ok) {
|
|
return [];
|
|
}
|
|
const artifacts = (await response.json()).artifacts;
|
|
const list = container.querySelector("ul");
|
|
list.replaceChildren();
|
|
for (const artifact of artifacts) {
|
|
const item = document.createElement("li");
|
|
const link = document.createElement("a");
|
|
if (artifact.view_url) {
|
|
link.href = artifact.view_url;
|
|
link.target = "_blank";
|
|
link.rel = "noopener";
|
|
link.textContent = `Open ${artifact.name}`;
|
|
} else {
|
|
link.href = artifact.url;
|
|
link.textContent = `Download ${artifact.name}`;
|
|
}
|
|
item.append(link);
|
|
list.append(item);
|
|
}
|
|
container.hidden = artifacts.length === 0;
|
|
return artifacts;
|
|
};
|
|
|
|
for (const container of document.querySelectorAll("#run-artifacts")) {
|
|
refreshArtifacts(container);
|
|
window.setInterval(() => refreshArtifacts(container), 2000);
|
|
}
|
|
|
|
for (const choice of document.querySelectorAll("button[data-execution-choice]")) {
|
|
choice.addEventListener("click", () => {
|
|
const control = choice.closest(".split-button");
|
|
const value = control?.querySelector("input[data-execution-value]");
|
|
const label = control?.querySelector(".button-label");
|
|
if (!control || !value || !label) {
|
|
return;
|
|
}
|
|
value.value = choice.dataset.value || "";
|
|
label.textContent = choice.dataset.label || "Run";
|
|
control.querySelectorAll("button[data-execution-choice]").forEach((option) => {
|
|
option.setAttribute("aria-pressed", String(option === choice));
|
|
});
|
|
const menu = control.querySelector("details");
|
|
if (menu) {
|
|
menu.open = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const form of document.querySelectorAll("form[data-wait-for-result]")) {
|
|
const result = form.querySelector(".task-result");
|
|
const controls = [...form.querySelectorAll("input, select, button")];
|
|
let waiting = false;
|
|
|
|
const warnBeforeLeaving = (event) => {
|
|
event.preventDefault();
|
|
event.returnValue = "";
|
|
};
|
|
|
|
const setWaiting = (value) => {
|
|
waiting = value;
|
|
form.classList.toggle("is-waiting", value);
|
|
controls.forEach((control) => {
|
|
control.disabled = value;
|
|
});
|
|
form.querySelectorAll("details.split-menu").forEach((menu) => {
|
|
menu.open = false;
|
|
});
|
|
if (value) {
|
|
window.addEventListener("beforeunload", warnBeforeLeaving);
|
|
} else {
|
|
window.removeEventListener("beforeunload", warnBeforeLeaving);
|
|
}
|
|
};
|
|
|
|
const showResult = (message, kind, detailUrl) => {
|
|
result.replaceChildren();
|
|
result.className = `task-result ${kind}`;
|
|
result.append(message);
|
|
if (detailUrl) {
|
|
const link = document.createElement("a");
|
|
link.href = detailUrl;
|
|
link.textContent = "View details";
|
|
result.append(" ", link);
|
|
}
|
|
};
|
|
|
|
const downloadArtifacts = async (run) => {
|
|
const response = await fetch(run.artifacts_url, { cache: "no-store" });
|
|
if (!response.ok) {
|
|
return;
|
|
}
|
|
const artifacts = (await response.json()).artifacts;
|
|
const artifact = artifacts.find((item) => !item.view_url);
|
|
if (!artifact) {
|
|
return;
|
|
}
|
|
const link = document.createElement("a");
|
|
link.href = artifact.url;
|
|
link.download = "";
|
|
document.body.append(link);
|
|
link.click();
|
|
link.remove();
|
|
};
|
|
|
|
const waitForRun = async (run) => {
|
|
while (true) {
|
|
const response = await fetch(run.state_url, { cache: "no-store" });
|
|
if (!response.ok) {
|
|
throw new Error("Could not read task status.");
|
|
}
|
|
const state = await response.json();
|
|
if (state.status !== "queued" && state.status !== "running") {
|
|
if (state.status === "succeeded") {
|
|
showResult("Completed successfully.", "succeeded", run.detail_url);
|
|
if (form.dataset.downloadArtifacts !== undefined) {
|
|
await downloadArtifacts(run);
|
|
}
|
|
} else {
|
|
showResult(`Finished with status: ${state.status}.`, "failed", run.detail_url);
|
|
}
|
|
return;
|
|
}
|
|
await new Promise((resolve) => window.setTimeout(resolve, 500));
|
|
}
|
|
};
|
|
|
|
form.addEventListener("submit", async (event) => {
|
|
if (waiting) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
if (!form.reportValidity()) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
// Disabled fields are omitted from FormData, so collect values first.
|
|
const formData = new FormData(form);
|
|
setWaiting(true);
|
|
showResult("Running...", "running");
|
|
try {
|
|
const response = await fetch(form.action, {
|
|
method: "POST",
|
|
body: formData,
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
const messages = data.errors ? Object.values(data.errors).join(" ") : "Could not start task.";
|
|
throw new Error(messages);
|
|
}
|
|
await waitForRun(data);
|
|
} catch (error) {
|
|
showResult(error instanceof Error ? error.message : "Could not run task.", "failed");
|
|
} finally {
|
|
setWaiting(false);
|
|
}
|
|
});
|
|
}
|